Pier Angelo Vendrame pushed to branch tor-browser-102.12.0esr-12.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
db0a953f
by Henry Wilkes at 2023-06-07T14:09:59+01:00
-
e86c7eaf
by Henry Wilkes at 2023-06-07T14:09:59+01:00
7 changed files:
- browser/components/torpreferences/content/builtinBridgeDialog.jsm
- browser/components/torpreferences/content/builtinBridgeDialog.xhtml
- browser/components/torpreferences/content/connectionPane.js
- browser/components/torpreferences/content/connectionPane.xhtml
- browser/components/torpreferences/content/torPreferences.css
- browser/modules/TorStrings.jsm
- toolkit/torbutton/chrome/locale/en-US/settings.properties
Changes:
| ... | ... | @@ -17,82 +17,81 @@ const { TorConnect, TorConnectTopics } = ChromeUtils.import( |
| 17 | 17 | );
|
| 18 | 18 | |
| 19 | 19 | class BuiltinBridgeDialog {
|
| 20 | + /**
|
|
| 21 | + * Create a new instance.
|
|
| 22 | + *
|
|
| 23 | + * @param {Function} onSubmit - A callback for when the user accepts the
|
|
| 24 | + * dialog selection.
|
|
| 25 | + */
|
|
| 20 | 26 | constructor(onSubmit) {
|
| 21 | 27 | this.onSubmit = onSubmit;
|
| 22 | - this._dialog = null;
|
|
| 23 | 28 | this._acceptButton = null;
|
| 24 | 29 | }
|
| 25 | 30 | |
| 26 | - static get selectors() {
|
|
| 27 | - return {
|
|
| 28 | - description: "#torPreferences-builtinBridge-description",
|
|
| 29 | - radiogroup: "#torPreferences-builtinBridge-typeSelection",
|
|
| 30 | - obfsRadio: "#torPreferences-builtinBridges-radioObfs",
|
|
| 31 | - obfsDescr: "#torPreferences-builtinBridges-descrObfs",
|
|
| 32 | - snowflakeRadio: "#torPreferences-builtinBridges-radioSnowflake",
|
|
| 33 | - snowflakeDescr: "#torPreferences-builtinBridges-descrSnowflake",
|
|
| 34 | - meekAzureRadio: "#torPreferences-builtinBridges-radioMeekAzure",
|
|
| 35 | - meekAzureDescr: "#torPreferences-builtinBridges-descrMeekAzure",
|
|
| 36 | - };
|
|
| 37 | - }
|
|
| 38 | - |
|
| 39 | - _populateXUL(window, aDialog) {
|
|
| 40 | - const selectors = BuiltinBridgeDialog.selectors;
|
|
| 41 | - |
|
| 42 | - this._dialog = aDialog;
|
|
| 43 | - const dialogWin = this._dialog.parentElement;
|
|
| 31 | + _populateXUL(window, dialog) {
|
|
| 32 | + const dialogWin = dialog.parentElement;
|
|
| 44 | 33 | dialogWin.setAttribute("title", TorStrings.settings.builtinBridgeHeader);
|
| 45 | 34 | |
| 46 | - this._dialog.querySelector(selectors.description).textContent =
|
|
| 47 | - TorStrings.settings.builtinBridgeDescription2;
|
|
| 35 | + dialog.querySelector(
|
|
| 36 | + "#torPreferences-builtinBridge-description"
|
|
| 37 | + ).textContent = TorStrings.settings.builtinBridgeDescription2;
|
|
| 48 | 38 | |
| 49 | - this._acceptButton = this._dialog.getButton("accept");
|
|
| 39 | + this._acceptButton = dialog.getButton("accept");
|
|
| 50 | 40 | this.onTorStateChange();
|
| 51 | 41 | |
| 52 | - let radioGroup = this._dialog.querySelector(selectors.radiogroup);
|
|
| 42 | + const radioGroup = dialog.querySelector(
|
|
| 43 | + "#torPreferences-builtinBridge-typeSelection"
|
|
| 44 | + );
|
|
| 53 | 45 | |
| 54 | - let types = {
|
|
| 46 | + const typeStrings = {
|
|
| 55 | 47 | obfs4: {
|
| 56 | - elemRadio: this._dialog.querySelector(selectors.obfsRadio),
|
|
| 57 | - elemDescr: this._dialog.querySelector(selectors.obfsDescr),
|
|
| 58 | 48 | label: TorStrings.settings.builtinBridgeObfs4Title,
|
| 59 | 49 | descr: TorStrings.settings.builtinBridgeObfs4Description2,
|
| 60 | 50 | },
|
| 61 | 51 | snowflake: {
|
| 62 | - elemRadio: this._dialog.querySelector(selectors.snowflakeRadio),
|
|
| 63 | - elemDescr: this._dialog.querySelector(selectors.snowflakeDescr),
|
|
| 64 | 52 | label: TorStrings.settings.builtinBridgeSnowflake,
|
| 65 | 53 | descr: TorStrings.settings.builtinBridgeSnowflakeDescription2,
|
| 66 | 54 | },
|
| 67 | 55 | "meek-azure": {
|
| 68 | - elemRadio: this._dialog.querySelector(selectors.meekAzureRadio),
|
|
| 69 | - elemDescr: this._dialog.querySelector(selectors.meekAzureDescr),
|
|
| 70 | 56 | label: TorStrings.settings.builtinBridgeMeekAzure,
|
| 71 | 57 | descr: TorStrings.settings.builtinBridgeMeekAzureDescription2,
|
| 72 | 58 | },
|
| 73 | 59 | };
|
| 74 | 60 | |
| 75 | - TorBuiltinBridgeTypes.forEach(type => {
|
|
| 76 | - types[type].elemRadio.setAttribute("label", types[type].label);
|
|
| 77 | - types[type].elemRadio.setAttribute("hidden", "false");
|
|
| 78 | - types[type].elemDescr.textContent = types[type].descr;
|
|
| 79 | - types[type].elemDescr.removeAttribute("hidden");
|
|
| 80 | - });
|
|
| 81 | - |
|
| 82 | - if (
|
|
| 61 | + const currentBuiltinType =
|
|
| 83 | 62 | TorSettings.bridges.enabled &&
|
| 84 | 63 | TorSettings.bridges.source == TorBridgeSource.BuiltIn
|
| 85 | - ) {
|
|
| 86 | - radioGroup.selectedItem =
|
|
| 87 | - types[TorSettings.bridges.builtin_type]?.elemRadio;
|
|
| 64 | + ? TorSettings.bridges.builtin_type
|
|
| 65 | + : null;
|
|
| 66 | + if (currentBuiltinType) {
|
|
| 67 | + radioGroup.value = currentBuiltinType;
|
|
| 88 | 68 | } else {
|
| 89 | 69 | radioGroup.selectedItem = null;
|
| 90 | 70 | }
|
| 91 | 71 | |
| 92 | - this._dialog.addEventListener("dialogaccept", () => {
|
|
| 72 | + for (const optionEl of radioGroup.querySelectorAll(
|
|
| 73 | + ".builtin-bridges-option"
|
|
| 74 | + )) {
|
|
| 75 | + const radio = optionEl.querySelector("radio");
|
|
| 76 | + const type = radio.value;
|
|
| 77 | + optionEl.hidden = !TorBuiltinBridgeTypes.includes(type);
|
|
| 78 | + radio.label = typeStrings[type].label;
|
|
| 79 | + optionEl.querySelector(
|
|
| 80 | + ".builtin-bridges-option-description"
|
|
| 81 | + ).textContent = typeStrings[type].descr;
|
|
| 82 | + optionEl.querySelector(
|
|
| 83 | + ".torPreferences-current-bridge-label"
|
|
| 84 | + ).textContent = TorStrings.settings.currentBridge;
|
|
| 85 | + optionEl.classList.toggle(
|
|
| 86 | + "current-builtin-bridge-type",
|
|
| 87 | + type === currentBuiltinType
|
|
| 88 | + );
|
|
| 89 | + }
|
|
| 90 | + |
|
| 91 | + dialog.addEventListener("dialogaccept", () => {
|
|
| 93 | 92 | this.onSubmit(radioGroup.value, TorConnect.canBeginBootstrap);
|
| 94 | 93 | });
|
| 95 | - this._dialog.addEventListener("dialoghelp", e => {
|
|
| 94 | + dialog.addEventListener("dialoghelp", e => {
|
|
| 96 | 95 | window.top.openTrustedLinkIn(
|
| 97 | 96 | TorStrings.settings.learnMoreCircumventionURL,
|
| 98 | 97 | "tab"
|
| ... | ... | @@ -100,8 +99,8 @@ class BuiltinBridgeDialog { |
| 100 | 99 | });
|
| 101 | 100 | |
| 102 | 101 | // Hack: see the CSS
|
| 103 | - this._dialog.style.minWidth = "0";
|
|
| 104 | - this._dialog.style.minHeight = "0";
|
|
| 102 | + dialog.style.minWidth = "0";
|
|
| 103 | + dialog.style.minHeight = "0";
|
|
| 105 | 104 | |
| 106 | 105 | Services.obs.addObserver(this, TorConnectTopics.StateChange);
|
| 107 | 106 | }
|
| ... | ... | @@ -8,16 +8,57 @@ |
| 8 | 8 | xmlns:html="http://www.w3.org/1999/xhtml">
|
| 9 | 9 | <dialog id="torPreferences-builtinBridge-dialog"
|
| 10 | 10 | buttons="help,accept,cancel">
|
| 11 | - <description>
|
|
| 12 | - <html:div id="torPreferences-builtinBridge-description">​<br/>​</html:div>
|
|
| 11 | + <description id="torPreferences-builtinBridge-description">
|
|
| 13 | 12 | </description>
|
| 14 | 13 | <radiogroup id="torPreferences-builtinBridge-typeSelection">
|
| 15 | - <radio id="torPreferences-builtinBridges-radioObfs" value="obfs4" hidden="true"/>
|
|
| 16 | - <html:div id="torPreferences-builtinBridges-descrObfs" class="indent" hidden="true">​</html:div>
|
|
| 17 | - <radio id="torPreferences-builtinBridges-radioSnowflake" value="snowflake" hidden="true"/>
|
|
| 18 | - <html:div id="torPreferences-builtinBridges-descrSnowflake" class="indent" hidden="true">​</html:div>
|
|
| 19 | - <radio id="torPreferences-builtinBridges-radioMeekAzure" value="meek-azure" hidden="true"/>
|
|
| 20 | - <html:div id="torPreferences-builtinBridges-descrMeekAzure" class="indent" hidden="true">​</html:div>
|
|
| 14 | + <vbox class="builtin-bridges-option">
|
|
| 15 | + <hbox>
|
|
| 16 | + <!-- The radio option is described by both the "Current bridge" label
|
|
| 17 | + - and the full description. If the "Connected" label is hidden, then
|
|
| 18 | + - only the latter description should contribute. -->
|
|
| 19 | + <radio aria-describedby="obfs-bridges-current obfs-bridges-description"
|
|
| 20 | + value="obfs4"/>
|
|
| 21 | + <html:span class="torPreferences-current-bridge-badge">
|
|
| 22 | + <image class="torPreferences-current-bridge-icon"/>
|
|
| 23 | + <html:span id="obfs-bridges-current"
|
|
| 24 | + class="torPreferences-current-bridge-label">
|
|
| 25 | + </html:span>
|
|
| 26 | + </html:span>
|
|
| 27 | + </hbox>
|
|
| 28 | + <html:div id="obfs-bridges-description"
|
|
| 29 | + class="indent builtin-bridges-option-description">
|
|
| 30 | + </html:div>
|
|
| 31 | + </vbox>
|
|
| 32 | + <vbox class="builtin-bridges-option">
|
|
| 33 | + <hbox>
|
|
| 34 | + <radio aria-describedby="snowflake-bridges-current snowflake-bridges-description"
|
|
| 35 | + value="snowflake"/>
|
|
| 36 | + <html:span class="torPreferences-current-bridge-badge">
|
|
| 37 | + <image class="torPreferences-current-bridge-icon"/>
|
|
| 38 | + <html:span id="snowflake-bridges-current"
|
|
| 39 | + class="torPreferences-current-bridge-label">
|
|
| 40 | + </html:span>
|
|
| 41 | + </html:span>
|
|
| 42 | + </hbox>
|
|
| 43 | + <html:div id="snowflake-bridges-description"
|
|
| 44 | + class="indent builtin-bridges-option-description">
|
|
| 45 | + </html:div>
|
|
| 46 | + </vbox>
|
|
| 47 | + <vbox class="builtin-bridges-option">
|
|
| 48 | + <hbox>
|
|
| 49 | + <radio aria-describedby="meek-bridges-current meek-bridges-description"
|
|
| 50 | + value="meek-azure"/>
|
|
| 51 | + <html:span class="torPreferences-current-bridge-badge">
|
|
| 52 | + <image class="torPreferences-current-bridge-icon"/>
|
|
| 53 | + <html:span id="meek-bridges-current"
|
|
| 54 | + class="torPreferences-current-bridge-label">
|
|
| 55 | + </html:span>
|
|
| 56 | + </html:span>
|
|
| 57 | + </hbox>
|
|
| 58 | + <html:div id="meek-bridges-description"
|
|
| 59 | + class="indent builtin-bridges-option-description">
|
|
| 60 | + </html:div>
|
|
| 61 | + </vbox>
|
|
| 21 | 62 | </radiogroup>
|
| 22 | 63 | <script type="application/javascript"><![CDATA[
|
| 23 | 64 | "use strict";
|
| ... | ... | @@ -124,7 +124,7 @@ const gConnectionPane = (function() { |
| 124 | 124 | cardId: ".torPreferences-bridgeCard-id",
|
| 125 | 125 | cardHeadingManualLink: ".torPreferences-bridgeCard-manualLink",
|
| 126 | 126 | cardHeadingAddr: ".torPreferences-bridgeCard-headingAddr",
|
| 127 | - cardConnectedLabel: ".torPreferences-bridgeCard-connectedLabel",
|
|
| 127 | + cardConnectedLabel: ".torPreferences-current-bridge-label",
|
|
| 128 | 128 | cardOptions: ".torPreferences-bridgeCard-options",
|
| 129 | 129 | cardMenu: "#torPreferences-bridgeCard-menu",
|
| 130 | 130 | cardQrGrid: ".torPreferences-bridgeCard-grid",
|
| ... | ... | @@ -168,7 +168,7 @@ const gConnectionPane = (function() { |
| 168 | 168 | |
| 169 | 169 | _controller: null,
|
| 170 | 170 | |
| 171 | - _currentBridge: "",
|
|
| 171 | + _currentBridgeId: null,
|
|
| 172 | 172 | |
| 173 | 173 | // populate xul with strings and cache the relevant elements
|
| 174 | 174 | _populateXUL() {
|
| ... | ... | @@ -471,7 +471,7 @@ const gConnectionPane = (function() { |
| 471 | 471 | }
|
| 472 | 472 | bridgeTemplate.querySelector(
|
| 473 | 473 | selectors.bridges.cardConnectedLabel
|
| 474 | - ).textContent = TorStrings.settings.statusTorConnected;
|
|
| 474 | + ).textContent = TorStrings.settings.connectedBridge;
|
|
| 475 | 475 | bridgeTemplate
|
| 476 | 476 | .querySelector(selectors.bridges.cardCopy)
|
| 477 | 477 | .setAttribute("label", TorStrings.settings.bridgeCopy);
|
| ... | ... | @@ -607,7 +607,7 @@ const gConnectionPane = (function() { |
| 607 | 607 | restoreTimeout = null;
|
| 608 | 608 | }, RESTORE_TIME);
|
| 609 | 609 | });
|
| 610 | - if (details && details.id === this._currentBridge) {
|
|
| 610 | + if (details?.id && details.id === this._currentBridgeId) {
|
|
| 611 | 611 | card.classList.add("currently-connected");
|
| 612 | 612 | bridgeCards.prepend(card);
|
| 613 | 613 | } else {
|
| ... | ... | @@ -714,9 +714,9 @@ const gConnectionPane = (function() { |
| 714 | 714 | // Add only the new strings that remained in the set
|
| 715 | 715 | for (const bridge of newStrings) {
|
| 716 | 716 | if (shownCards >= toShow) {
|
| 717 | - if (this._currentBridge === "") {
|
|
| 717 | + if (!this._currentBridgeId) {
|
|
| 718 | 718 | break;
|
| 719 | - } else if (!bridge.includes(this._currentBridge)) {
|
|
| 719 | + } else if (!bridge.includes(this._currentBridgeId)) {
|
|
| 720 | 720 | continue;
|
| 721 | 721 | }
|
| 722 | 722 | }
|
| ... | ... | @@ -787,7 +787,7 @@ const gConnectionPane = (function() { |
| 787 | 787 | )) {
|
| 788 | 788 | card.classList.remove("currently-connected");
|
| 789 | 789 | }
|
| 790 | - if (this._currentBridge === "") {
|
|
| 790 | + if (!this._currentBridgeId) {
|
|
| 791 | 791 | return;
|
| 792 | 792 | }
|
| 793 | 793 | // Make sure we have the connected bridge in the list
|
| ... | ... | @@ -796,7 +796,7 @@ const gConnectionPane = (function() { |
| 796 | 796 | // case also with built-in bridges!). E.g., one line for the IPv4
|
| 797 | 797 | // address and one for the IPv6 address, so use querySelectorAll
|
| 798 | 798 | const cards = bridgeCards.querySelectorAll(
|
| 799 | - `[data-bridge-id="${this._currentBridge}"]`
|
|
| 799 | + `[data-bridge-id="${this._currentBridgeId}"]`
|
|
| 800 | 800 | );
|
| 801 | 801 | for (const card of cards) {
|
| 802 | 802 | card.classList.add("currently-connected");
|
| ... | ... | @@ -823,6 +823,12 @@ const gConnectionPane = (function() { |
| 823 | 823 | // this circuit to check if the bridge can be used. We do this by
|
| 824 | 824 | // checking if the stream has SOCKS username, which actually contains
|
| 825 | 825 | // the destination of the stream.
|
| 826 | + // FIXME: We only know the currentBridge *after* a circuit event, but
|
|
| 827 | + // if the circuit event is sent *before* about:torpreferences is
|
|
| 828 | + // opened we will miss it. Therefore this approach only works if a
|
|
| 829 | + // circuit is created after opening about:torconnect. A dedicated
|
|
| 830 | + // backend outside of about:preferences would help, and could be
|
|
| 831 | + // shared with gTorCircuitPanel. See tor-browser#41700.
|
|
| 826 | 832 | this._controller.watchEvent(
|
| 827 | 833 | "STREAM",
|
| 828 | 834 | event =>
|
| ... | ... | @@ -836,10 +842,22 @@ const gConnectionPane = (function() { |
| 836 | 842 | }
|
| 837 | 843 | for (const status of circuitStatuses) {
|
| 838 | 844 | if (status.id === event.CircuitID && status.circuit.length) {
|
| 839 | - // The id in the circuit begins with a $ sign
|
|
| 840 | - const bridgeId = status.circuit[0][0].substring(1);
|
|
| 841 | - if (bridgeId !== this._currentBridge) {
|
|
| 842 | - this._currentBridge = bridgeId;
|
|
| 845 | + // The id in the circuit begins with a $ sign.
|
|
| 846 | + const id = status.circuit[0][0].replace(/^\$/, "");
|
|
| 847 | + if (id !== this._currentBridgeId) {
|
|
| 848 | + const bridge = (
|
|
| 849 | + await this._controller.getConf("bridge")
|
|
| 850 | + )?.find(
|
|
| 851 | + foundBridge =>
|
|
| 852 | + foundBridge.ID?.toUpperCase() === id.toUpperCase()
|
|
| 853 | + );
|
|
| 854 | + if (!bridge) {
|
|
| 855 | + // Either there is no bridge, or bridge with no
|
|
| 856 | + // fingerprint.
|
|
| 857 | + this._currentBridgeId = null;
|
|
| 858 | + } else {
|
|
| 859 | + this._currentBridgeId = id;
|
|
| 860 | + }
|
|
| 843 | 861 | this._updateConnectedBridges();
|
| 844 | 862 | }
|
| 845 | 863 | break;
|
| ... | ... | @@ -109,9 +109,9 @@ |
| 109 | 109 | <label class="torPreferences-bridgeCard-manualLink learnMore text-link stop-click" is="text-link"/>
|
| 110 | 110 | <html:div class="torPreferences-bridgeCard-headingAddr"/>
|
| 111 | 111 | <html:div class="torPreferences-bridgeCard-buttons">
|
| 112 | - <html:span class="torPreferences-bridgeCard-connectedBadge">
|
|
| 113 | - <image class="torPreferences-bridgeCard-connectedIcon"/>
|
|
| 114 | - <html:span class="torPreferences-bridgeCard-connectedLabel"/>
|
|
| 112 | + <html:span class="torPreferences-current-bridge-badge">
|
|
| 113 | + <image class="torPreferences-current-bridge-icon"/>
|
|
| 114 | + <html:span class="torPreferences-current-bridge-label"></html:span>
|
|
| 115 | 115 | </html:span>
|
| 116 | 116 | <html:button class="torPreferences-bridgeCard-options stop-click"/>
|
| 117 | 117 | </html:div>
|
| ... | ... | @@ -298,28 +298,38 @@ html:dir(rtl) input[type="checkbox"].toggle-button::before { |
| 298 | 298 | align-self: center;
|
| 299 | 299 | }
|
| 300 | 300 | |
| 301 | -.torPreferences-bridgeCard-connectedBadge {
|
|
| 301 | +.torPreferences-current-bridge-badge {
|
|
| 302 | + /* Hidden by default, otherwise display is "flex". */
|
|
| 302 | 303 | display: none;
|
| 303 | - margin-inline-end: 12px;
|
|
| 304 | - color: var(--purple-60);
|
|
| 305 | -}
|
|
| 306 | - |
|
| 307 | -@media (prefers-color-scheme: dark) {
|
|
| 308 | - .torPreferences-bridgeCard-connectedBadge {
|
|
| 309 | - color: var(--purple-30);
|
|
| 310 | - }
|
|
| 304 | + align-items: center;
|
|
| 305 | + font-size: 0.85em;
|
|
| 311 | 306 | }
|
| 312 | 307 | |
| 313 | -.currently-connected .torPreferences-bridgeCard-connectedBadge {
|
|
| 308 | +:is(
|
|
| 309 | + .builtin-bridges-option.current-builtin-bridge-type,
|
|
| 310 | + .torPreferences-bridgeCard.currently-connected
|
|
| 311 | +) .torPreferences-current-bridge-badge {
|
|
| 314 | 312 | display: flex;
|
| 315 | 313 | }
|
| 316 | 314 | |
| 317 | -.torPreferences-bridgeCard-connectedIcon {
|
|
| 315 | +.torPreferences-current-bridge-icon {
|
|
| 318 | 316 | margin-inline-start: 1px;
|
| 319 | 317 | margin-inline-end: 7px;
|
| 320 | 318 | list-style-image: url("chrome://browser/content/torpreferences/check.svg");
|
| 321 | 319 | -moz-context-properties: fill;
|
| 322 | 320 | fill: currentColor;
|
| 321 | + flex: 0 0 auto;
|
|
| 322 | +}
|
|
| 323 | + |
|
| 324 | +.torPreferences-bridgeCard .torPreferences-current-bridge-badge {
|
|
| 325 | + color: var(--purple-60);
|
|
| 326 | + margin-inline-end: 12px;
|
|
| 327 | +}
|
|
| 328 | + |
|
| 329 | +@media (prefers-color-scheme: dark) {
|
|
| 330 | + .torPreferences-bridgeCard .torPreferences-current-bridge-badge {
|
|
| 331 | + color: var(--purple-30);
|
|
| 332 | + }
|
|
| 323 | 333 | }
|
| 324 | 334 | |
| 325 | 335 | .torPreferences-bridgeCard-options {
|
| ... | ... | @@ -564,6 +574,10 @@ dialog#torPreferences-requestBridge-dialog > hbox { |
| 564 | 574 | font-weight: 700;
|
| 565 | 575 | }
|
| 566 | 576 | |
| 577 | +.builtin-bridges-option .torPreferences-current-bridge-badge {
|
|
| 578 | + color: var(--in-content-accent-color);
|
|
| 579 | +}
|
|
| 580 | + |
|
| 567 | 581 | /* Request bridge dialog */
|
| 568 | 582 | /*
|
| 569 | 583 | This hbox is hidden by css here by default so that the
|
| ... | ... | @@ -105,6 +105,8 @@ const Loader = { |
| 105 | 105 | bridgeCurrentDescription:
|
| 106 | 106 | "You can keep one or more bridges saved, and Tor will choose which one to use when you connect. Tor will automatically switch to use another bridge when needed.",
|
| 107 | 107 | bridgeId: "%1$S bridge: %2$S",
|
| 108 | + currentBridge: "Current bridge",
|
|
| 109 | + connectedBridge: "Connected",
|
|
| 108 | 110 | remove: "Remove",
|
| 109 | 111 | bridgeDisableBuiltIn: "Disable built-in bridges",
|
| 110 | 112 | bridgeShare:
|
| ... | ... | @@ -39,6 +39,8 @@ settings.bridgeCurrentDescription=You can keep one or more bridges saved, and To |
| 39 | 39 | |
| 40 | 40 | # Translation note: %1$S = bridge type; %2$S = bridge emoji id
|
| 41 | 41 | settings.bridgeId=%1$S bridge: %2$S
|
| 42 | +settings.connectedBridge=Connected
|
|
| 43 | +settings.currentBridge=Current bridge
|
|
| 42 | 44 | settings.remove=Remove
|
| 43 | 45 | settings.bridgeDisableBuiltIn=Disable built-in bridges
|
| 44 | 46 | settings.bridgeShare=Share this bridge using the QR code or by copying its address:
|