richard pushed to branch tor-browser-115.9.0esr-13.5-1 at The Tor Project / Applications / Tor Browser
Commits:
-
99d4a1de
by Henry Wilkes at 2024-03-25T09:42:29+00:00
-
56ce4cc1
by Henry Wilkes at 2024-03-25T09:42:30+00:00
-
76f869d5
by Henry Wilkes at 2024-03-25T09:42:31+00:00
-
724dfc5f
by Henry Wilkes at 2024-03-25T09:42:31+00:00
-
966b7f30
by Henry Wilkes at 2024-03-25T09:42:32+00:00
11 changed files:
- browser/base/content/appmenu-viewcache.inc.xhtml
- browser/base/content/browser-menubar.inc
- browser/base/content/navigator-toolbox.inc.xhtml
- browser/components/torcircuit/content/torCircuitPanel.css
- browser/components/torcircuit/content/torCircuitPanel.inc.xhtml
- browser/components/torcircuit/content/torCircuitPanel.js
- browser/locales/en-US/browser/tor-browser.ftl
- toolkit/torbutton/chrome/locale/en-US/torbutton.dtd
- toolkit/torbutton/chrome/locale/en-US/torbutton.properties
- tools/torbrowser/l10n/migrate.py
- + tools/torbrowser/l10n/migrations/bug-42209-tor-circuit.py
Changes:
| ... | ... | @@ -62,7 +62,7 @@ |
| 62 | 62 | <toolbarbutton id="appMenuNewCircuit"
|
| 63 | 63 | class="subviewbutton"
|
| 64 | 64 | key="new-circuit-key"
|
| 65 | - label="&torbutton.context_menu.new_circuit_sentence_case;"
|
|
| 65 | + data-l10n-id="appmenuitem-new-tor-circuit"
|
|
| 66 | 66 | oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
|
| 67 | 67 | <toolbarseparator/>
|
| 68 | 68 | <toolbarbutton id="appMenu-bookmarks-button"
|
| ... | ... | @@ -32,9 +32,7 @@ |
| 32 | 32 | <menuitem id="menu_newIdentity"
|
| 33 | 33 | key="new-identity-key" data-l10n-id="menu-new-identity"/>
|
| 34 | 34 | <menuitem id="menu_newCircuit"
|
| 35 | - accesskey="&torbutton.context_menu.new_circuit_key;"
|
|
| 36 | - key="new-circuit-key"
|
|
| 37 | - label="&torbutton.context_menu.new_circuit;"
|
|
| 35 | + key="new-circuit-key" data-l10n-id="menu-new-tor-circuit"
|
|
| 38 | 36 | oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
|
| 39 | 37 | <menuseparator/>
|
| 40 | 38 | <menuitem id="menu_openLocation"
|
| ... | ... | @@ -198,7 +198,7 @@ |
| 198 | 198 | role="button"
|
| 199 | 199 | class="identity-box-button"
|
| 200 | 200 | align="center"
|
| 201 | - tooltiptext="&torbutton.circuit_display.title;"
|
|
| 201 | + data-l10n-id="tor-circuit-urlbar-button"
|
|
| 202 | 202 | hidden="true">
|
| 203 | 203 | <image id="tor-circuit-button-icon"/>
|
| 204 | 204 | </box>
|
| ... | ... | @@ -621,9 +621,8 @@ |
| 621 | 621 | data-l10n-id="toolbar-new-identity"/>
|
| 622 | 622 | |
| 623 | 623 | <toolbarbutton id="new-circuit-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
| 624 | - label="&torbutton.context_menu.new_circuit;"
|
|
| 625 | - oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"
|
|
| 626 | - tooltiptext="&torbutton.context_menu.new_circuit;"/>
|
|
| 624 | + data-l10n-id="toolbar-new-tor-circuit"
|
|
| 625 | + oncommand="TorDomainIsolator.newCircuitForBrowser(gBrowser);"/>
|
|
| 627 | 626 | |
| 628 | 627 | <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
|
| 629 | 628 | observes="View:FullScreen"
|
| ... | ... | @@ -120,6 +120,15 @@ |
| 120 | 120 | background-repeat: no-repeat;
|
| 121 | 121 | }
|
| 122 | 122 | |
| 123 | +.tor-circuit-node-item:not([hidden]) {
|
|
| 124 | + display: flex;
|
|
| 125 | + align-items: baseline;
|
|
| 126 | +}
|
|
| 127 | + |
|
| 128 | +.tor-circuit-node-item > * {
|
|
| 129 | + flex: 0 0 auto;
|
|
| 130 | +}
|
|
| 131 | + |
|
| 123 | 132 | @media (prefers-color-scheme: dark) {
|
| 124 | 133 | .tor-circuit-node-item {
|
| 125 | 134 | /* Light Gray 90 */
|
| ... | ... | @@ -146,9 +155,9 @@ |
| 146 | 155 | .tor-circuit-region-flag {
|
| 147 | 156 | margin-inline-end: 0.5em;
|
| 148 | 157 | height: 16px;
|
| 149 | - vertical-align: sub;
|
|
| 158 | + align-self: center;
|
|
| 150 | 159 | /* Don't occupy any vertical height. */
|
| 151 | - margin-block-start: -16px;
|
|
| 160 | + margin-block: -8px;
|
|
| 152 | 161 | }
|
| 153 | 162 | |
| 154 | 163 | .tor-circuit-region-flag.no-region-flag-src {
|
| ... | ... | @@ -158,7 +167,7 @@ |
| 158 | 167 | .tor-circuit-addresses {
|
| 159 | 168 | font-size: smaller;
|
| 160 | 169 | font-family: monospace;
|
| 161 | - margin-inline-start: 0.25em;
|
|
| 170 | + margin-inline-start: 0.75em;
|
|
| 162 | 171 | }
|
| 163 | 172 | |
| 164 | 173 | /* Footer buttons */
|
| ... | ... | @@ -19,44 +19,67 @@ |
| 19 | 19 | <vbox class="panel-header">
|
| 20 | 20 | <html:h1 id="tor-circuit-heading"></html:h1>
|
| 21 | 21 | <html:div id="tor-circuit-alias" hidden="hidden">
|
| 22 | - <html:img src="chrome://browser/content/tor-circuit-redirect.svg"
|
|
| 23 | - alt="" />
|
|
| 22 | + <html:img
|
|
| 23 | + src="chrome://browser/content/tor-circuit-redirect.svg"
|
|
| 24 | + alt=""
|
|
| 25 | + />
|
|
| 24 | 26 | <html:p id="tor-circuit-alias-label">
|
| 25 | - <html:a />
|
|
| 27 | + <html:a class="tor-circuit-alias-link" data-l10n-name="alias-link" />
|
|
| 26 | 28 | </html:p>
|
| 27 | 29 | </html:div>
|
| 28 | 30 | </vbox>
|
| 29 | 31 | <toolbarseparator/>
|
| 30 | 32 | <vbox id="tor-circuit-panel-body" class="panel-subview-body">
|
| 31 | - <html:p id="tor-circuit-node-list-name">&torbutton.circuit_display.title;</html:p>
|
|
| 33 | + <html:p
|
|
| 34 | + id="tor-circuit-node-list-name"
|
|
| 35 | + data-l10n-id="tor-circuit-panel-node-list-introduction"
|
|
| 36 | + ></html:p>
|
|
| 32 | 37 | <html:ol id="tor-circuit-node-list">
|
| 33 | - <html:li id="tor-circuit-start-item"
|
|
| 34 | - class="tor-circuit-node-item">
|
|
| 35 | - </html:li>
|
|
| 36 | - <html:li id="tor-circuit-relays-item"
|
|
| 37 | - class="tor-circuit-node-item tor-circuit-relays-item">
|
|
| 38 | - </html:li>
|
|
| 39 | - <html:li id="tor-circuit-end-item"
|
|
| 40 | - class="tor-circuit-node-item">
|
|
| 41 | - </html:li>
|
|
| 38 | + <html:li
|
|
| 39 | + id="tor-circuit-start-item"
|
|
| 40 | + class="tor-circuit-node-item"
|
|
| 41 | + data-l10n-id="tor-circuit-panel-node-browser"
|
|
| 42 | + ></html:li>
|
|
| 43 | + <html:li
|
|
| 44 | + id="tor-circuit-relays-item"
|
|
| 45 | + class="tor-circuit-node-item tor-circuit-relays-item"
|
|
| 46 | + data-l10n-id="tor-circuit-panel-node-onion-relays"
|
|
| 47 | + ></html:li>
|
|
| 48 | + <html:li
|
|
| 49 | + id="tor-circuit-end-item"
|
|
| 50 | + class="tor-circuit-node-item"
|
|
| 51 | + ></html:li>
|
|
| 42 | 52 | </html:ol>
|
| 53 | + <html:template id="tor-circuit-node-item-template">
|
|
| 54 | + <html:li class="tor-circuit-node-item">
|
|
| 55 | + <html:img class="tor-circuit-region-flag" alt="" />
|
|
| 56 | + <html:span class="tor-circuit-node-name"></html:span>
|
|
| 57 | + <html:span class="tor-circuit-addresses"></html:span>
|
|
| 58 | + </html:li>
|
|
| 59 | + </html:template>
|
|
| 43 | 60 | </vbox>
|
| 44 | 61 | <toolbarseparator/>
|
| 45 | 62 | <!-- NOTE: To fully benefit from the .subviewbutton styling, we need to use
|
| 46 | 63 | - a xul:toolbarbutton rather than a html:button.
|
| 47 | 64 | - By default, a xul:toolbarbutton is not focusable so we need to add
|
| 48 | 65 | - tabindex. -->
|
| 49 | - <toolbarbutton id="tor-circuit-new-circuit"
|
|
| 50 | - class="subviewbutton panel-subview-footer-button tor-circuit-button"
|
|
| 51 | - tabindex="0"
|
|
| 52 | - aria-labelledby="tor-circuit-new-circuit-label"
|
|
| 53 | - aria-describedby="tor-circuit-new-circuit-description">
|
|
| 66 | + <toolbarbutton
|
|
| 67 | + id="tor-circuit-new-circuit"
|
|
| 68 | + class="subviewbutton panel-subview-footer-button tor-circuit-button"
|
|
| 69 | + tabindex="0"
|
|
| 70 | + aria-labelledby="tor-circuit-new-circuit-label"
|
|
| 71 | + aria-describedby="tor-circuit-new-circuit-description"
|
|
| 72 | + >
|
|
| 54 | 73 | <vbox align="start">
|
| 55 | - <label id="tor-circuit-new-circuit-label"
|
|
| 56 | - class="toolbarbutton-text"
|
|
| 57 | - value="&torbutton.context_menu.new_circuit_sentence_case;"/>
|
|
| 58 | - <label id="tor-circuit-new-circuit-description"
|
|
| 59 | - class="tor-circuit-button-description"/>
|
|
| 74 | + <label
|
|
| 75 | + id="tor-circuit-new-circuit-label"
|
|
| 76 | + class="toolbarbutton-text"
|
|
| 77 | + data-l10n-id="tor-circuit-panel-new-button"
|
|
| 78 | + />
|
|
| 79 | + <label
|
|
| 80 | + id="tor-circuit-new-circuit-description"
|
|
| 81 | + class="tor-circuit-button-description"
|
|
| 82 | + />
|
|
| 60 | 83 | </vbox>
|
| 61 | 84 | </toolbarbutton>
|
| 62 | 85 | </vbox>
|
| ... | ... | @@ -34,6 +34,12 @@ var gTorCircuitPanel = { |
| 34 | 34 | * @type {bool}
|
| 35 | 35 | */
|
| 36 | 36 | _isActive: false,
|
| 37 | + /**
|
|
| 38 | + * The template element for circuit nodes.
|
|
| 39 | + *
|
|
| 40 | + * @type {HTMLTemplateElement?}
|
|
| 41 | + */
|
|
| 42 | + _nodeItemTemplate: null,
|
|
| 37 | 43 | |
| 38 | 44 | /**
|
| 39 | 45 | * The topic on which circuit changes are broadcast.
|
| ... | ... | @@ -62,7 +68,6 @@ var gTorCircuitPanel = { |
| 62 | 68 | heading: document.getElementById("tor-circuit-heading"),
|
| 63 | 69 | alias: document.getElementById("tor-circuit-alias"),
|
| 64 | 70 | aliasLabel: document.getElementById("tor-circuit-alias-label"),
|
| 65 | - aliasLink: document.querySelector("#tor-circuit-alias-label a"),
|
|
| 66 | 71 | aliasMenu: document.getElementById("tor-circuit-panel-alias-menu"),
|
| 67 | 72 | list: document.getElementById("tor-circuit-node-list"),
|
| 68 | 73 | relaysItem: document.getElementById("tor-circuit-relays-item"),
|
| ... | ... | @@ -73,30 +78,24 @@ var gTorCircuitPanel = { |
| 73 | 78 | };
|
| 74 | 79 | this.toolbarButton = document.getElementById("tor-circuit-button");
|
| 75 | 80 | |
| 76 | - // TODO: These strings should be set in the HTML markup with fluent.
|
|
| 77 | - |
|
| 78 | - // NOTE: There is already whitespace before and after the link from the
|
|
| 79 | - // XHTML markup.
|
|
| 80 | - const [aliasBefore, aliasAfter] = this._getString(
|
|
| 81 | - "torbutton.circuit_display.connected-to-alias",
|
|
| 82 | - // Placeholder is replaced with the same placeholder. This is a bit of a
|
|
| 83 | - // hack since we want the inserted address to be the rich anchor
|
|
| 84 | - // element already in the DOM, rather than a plain address.
|
|
| 85 | - // We won't have to do this with fluent by using data-l10n-name on the
|
|
| 86 | - // anchor element.
|
|
| 87 | - ["%S"]
|
|
| 88 | - ).split("%S");
|
|
| 89 | - this._panelElements.aliasLabel.prepend(aliasBefore);
|
|
| 90 | - this._panelElements.aliasLabel.append(aliasAfter);
|
|
| 91 | - |
|
| 92 | - this._panelElements.aliasLink.addEventListener("click", event => {
|
|
| 81 | + // We add listeners for the .tor-circuit-alias-link.
|
|
| 82 | + // NOTE: We have to add the listeners to the parent element because the
|
|
| 83 | + // link (with data-l10n-name="alias-link") will be replaced with a new
|
|
| 84 | + // cloned instance every time the parent gets re-translated.
|
|
| 85 | + this._panelElements.aliasLabel.addEventListener("click", event => {
|
|
| 86 | + if (!this._aliasLink.contains(event.target)) {
|
|
| 87 | + return;
|
|
| 88 | + }
|
|
| 93 | 89 | event.preventDefault();
|
| 94 | 90 | if (event.button !== 0) {
|
| 95 | 91 | return;
|
| 96 | 92 | }
|
| 97 | 93 | this._openAlias("tab");
|
| 98 | 94 | });
|
| 99 | - this._panelElements.aliasLink.addEventListener("contextmenu", event => {
|
|
| 95 | + this._panelElements.aliasLabel.addEventListener("contextmenu", event => {
|
|
| 96 | + if (!this._aliasLink.contains(event.target)) {
|
|
| 97 | + return;
|
|
| 98 | + }
|
|
| 100 | 99 | event.preventDefault();
|
| 101 | 100 | this._panelElements.aliasMenu.openPopupAtScreen(
|
| 102 | 101 | event.screenX,
|
| ... | ... | @@ -119,21 +118,15 @@ var gTorCircuitPanel = { |
| 119 | 118 | document
|
| 120 | 119 | .getElementById("tor-circuit-panel-alias-menu-copy")
|
| 121 | 120 | .addEventListener("command", () => {
|
| 122 | - if (!this._panelElements.aliasLink.href) {
|
|
| 121 | + const alias = this._aliasLink?.href;
|
|
| 122 | + if (!alias) {
|
|
| 123 | 123 | return;
|
| 124 | 124 | }
|
| 125 | 125 | Cc["@mozilla.org/widget/clipboardhelper;1"]
|
| 126 | 126 | .getService(Ci.nsIClipboardHelper)
|
| 127 | - .copyString(this._panelElements.aliasLink.href);
|
|
| 127 | + .copyString(alias);
|
|
| 128 | 128 | });
|
| 129 | 129 | |
| 130 | - document.getElementById("tor-circuit-start-item").textContent =
|
|
| 131 | - this._getString("torbutton.circuit_display.this_browser");
|
|
| 132 | - |
|
| 133 | - this._panelElements.relaysItem.textContent = this._getString(
|
|
| 134 | - "torbutton.circuit_display.onion-site-relays"
|
|
| 135 | - );
|
|
| 136 | - |
|
| 137 | 130 | // Button is a xul:toolbarbutton, so we use "command" rather than "click".
|
| 138 | 131 | document
|
| 139 | 132 | .getElementById("tor-circuit-new-circuit")
|
| ... | ... | @@ -176,6 +169,13 @@ var gTorCircuitPanel = { |
| 176 | 169 | this.show();
|
| 177 | 170 | });
|
| 178 | 171 | |
| 172 | + this._nodeItemTemplate = document.getElementById(
|
|
| 173 | + "tor-circuit-node-item-template"
|
|
| 174 | + );
|
|
| 175 | + // Prepare the unknown region name for the current locale.
|
|
| 176 | + // NOTE: We expect this to complete before the first call to _updateBody.
|
|
| 177 | + this._localeChanged();
|
|
| 178 | + |
|
| 179 | 179 | this._locationListener = {
|
| 180 | 180 | onLocationChange: (webProgress, request, locationURI, flags) => {
|
| 181 | 181 | if (
|
| ... | ... | @@ -194,6 +194,7 @@ var gTorCircuitPanel = { |
| 194 | 194 | |
| 195 | 195 | // Get notifications for circuit changes.
|
| 196 | 196 | Services.obs.addObserver(this, this.TOR_CIRCUIT_TOPIC);
|
| 197 | + Services.obs.addObserver(this, "intl:app-locales-changed");
|
|
| 197 | 198 | },
|
| 198 | 199 | |
| 199 | 200 | /**
|
| ... | ... | @@ -203,15 +204,21 @@ var gTorCircuitPanel = { |
| 203 | 204 | this._isActive = false;
|
| 204 | 205 | gBrowser.removeProgressListener(this._locationListener);
|
| 205 | 206 | Services.obs.removeObserver(this, this.TOR_CIRCUIT_TOPIC);
|
| 207 | + Services.obs.removeObserver(this, "intl:app-locales-changed");
|
|
| 206 | 208 | },
|
| 207 | 209 | |
| 208 | 210 | /**
|
| 209 | 211 | * Observe circuit changes.
|
| 210 | 212 | */
|
| 211 | 213 | observe(subject, topic, data) {
|
| 212 | - if (topic === this.TOR_CIRCUIT_TOPIC) {
|
|
| 213 | - // TODO: Maybe check if we actually need to do something earlier.
|
|
| 214 | - this._updateCurrentBrowser();
|
|
| 214 | + switch (topic) {
|
|
| 215 | + case this.TOR_CIRCUIT_TOPIC:
|
|
| 216 | + // TODO: Maybe check if we actually need to do something earlier.
|
|
| 217 | + this._updateCurrentBrowser();
|
|
| 218 | + break;
|
|
| 219 | + case "intl:app-locales-changed":
|
|
| 220 | + this._localeChanged();
|
|
| 221 | + break;
|
|
| 215 | 222 | }
|
| 216 | 223 | },
|
| 217 | 224 | |
| ... | ... | @@ -231,6 +238,19 @@ var gTorCircuitPanel = { |
| 231 | 238 | this.panel.hidePopup();
|
| 232 | 239 | },
|
| 233 | 240 | |
| 241 | + /**
|
|
| 242 | + * Get the current alias link instance.
|
|
| 243 | + *
|
|
| 244 | + * Note that this element instance may change whenever its parent element
|
|
| 245 | + * (#tor-circuit-alias-label) is re-translated. Attributes should be copied to
|
|
| 246 | + * the new instance.
|
|
| 247 | + */
|
|
| 248 | + get _aliasLink() {
|
|
| 249 | + return this._panelElements.aliasLabel.querySelector(
|
|
| 250 | + ".tor-circuit-alias-link"
|
|
| 251 | + );
|
|
| 252 | + },
|
|
| 253 | + |
|
| 234 | 254 | /**
|
| 235 | 255 | * Open the onion alias present in the alias link.
|
| 236 | 256 | *
|
| ... | ... | @@ -238,12 +258,13 @@ var gTorCircuitPanel = { |
| 238 | 258 | * window.
|
| 239 | 259 | */
|
| 240 | 260 | _openAlias(where) {
|
| 241 | - if (!this._panelElements.aliasLink.href) {
|
|
| 261 | + const url = this._aliasLink?.href;
|
|
| 262 | + if (!url) {
|
|
| 242 | 263 | return;
|
| 243 | 264 | }
|
| 244 | 265 | // We hide the panel before opening the link.
|
| 245 | 266 | this.hide();
|
| 246 | - window.openWebLinkIn(this._panelElements.aliasLink.href, where);
|
|
| 267 | + window.openWebLinkIn(url, where);
|
|
| 247 | 268 | },
|
| 248 | 269 | |
| 249 | 270 | /**
|
| ... | ... | @@ -351,11 +372,6 @@ var gTorCircuitPanel = { |
| 351 | 372 | |
| 352 | 373 | this.toolbarButton.hidden = false;
|
| 353 | 374 | |
| 354 | - if (this.panel.state !== "open" && this.panel.state !== "showing") {
|
|
| 355 | - // Don't update the panel content if it is not open or about to open.
|
|
| 356 | - return;
|
|
| 357 | - }
|
|
| 358 | - |
|
| 359 | 375 | this._updateCircuitPanel();
|
| 360 | 376 | },
|
| 361 | 377 | |
| ... | ... | @@ -383,35 +399,15 @@ var gTorCircuitPanel = { |
| 383 | 399 | return alias;
|
| 384 | 400 | },
|
| 385 | 401 | |
| 386 | - /**
|
|
| 387 | - * Get a string from the properties bundle.
|
|
| 388 | - *
|
|
| 389 | - * @param {string} name - The string name.
|
|
| 390 | - * @param {string[]} args - The arguments to pass to the string.
|
|
| 391 | - *
|
|
| 392 | - * @returns {string} The string.
|
|
| 393 | - */
|
|
| 394 | - _getString(name, args = []) {
|
|
| 395 | - if (!this._stringBundle) {
|
|
| 396 | - this._stringBundle = Services.strings.createBundle(
|
|
| 397 | - "chrome://torbutton/locale/torbutton.properties"
|
|
| 398 | - );
|
|
| 399 | - }
|
|
| 400 | - try {
|
|
| 401 | - return this._stringBundle.formatStringFromName(name, args);
|
|
| 402 | - } catch {}
|
|
| 403 | - if (!this._fallbackStringBundle) {
|
|
| 404 | - this._fallbackStringBundle = Services.strings.createBundle(
|
|
| 405 | - "resource://torbutton/locale/en-US/torbutton.properties"
|
|
| 406 | - );
|
|
| 407 | - }
|
|
| 408 | - return this._fallbackStringBundle.formatStringFromName(name, args);
|
|
| 409 | - },
|
|
| 410 | - |
|
| 411 | 402 | /**
|
| 412 | 403 | * Updates the circuit display in the panel to show the current browser data.
|
| 413 | 404 | */
|
| 414 | 405 | _updateCircuitPanel() {
|
| 406 | + if (this.panel.state !== "open" && this.panel.state !== "showing") {
|
|
| 407 | + // Don't update the panel content if it is not open or about to open.
|
|
| 408 | + return;
|
|
| 409 | + }
|
|
| 410 | + |
|
| 415 | 411 | // NOTE: The _currentBrowserData.nodes data may be stale. In particular, the
|
| 416 | 412 | // circuit may have expired already, or we're still waiting on the new
|
| 417 | 413 | // circuit.
|
| ... | ... | @@ -426,6 +422,9 @@ var gTorCircuitPanel = { |
| 426 | 422 | this.hide();
|
| 427 | 423 | return;
|
| 428 | 424 | }
|
| 425 | + |
|
| 426 | + this._log.debug("Updating circuit panel");
|
|
| 427 | + |
|
| 429 | 428 | let domain = this._currentBrowserData.domain;
|
| 430 | 429 | const onionAlias = this._getOnionAlias(domain);
|
| 431 | 430 | |
| ... | ... | @@ -447,24 +446,31 @@ var gTorCircuitPanel = { |
| 447 | 446 | * @param {string?} scheme - The scheme in use for the current domain.
|
| 448 | 447 | */
|
| 449 | 448 | _updateHeading(domain, onionAlias, scheme) {
|
| 450 | - this._panelElements.heading.textContent = this._getString(
|
|
| 451 | - "torbutton.circuit_display.heading",
|
|
| 449 | + document.l10n.setAttributes(
|
|
| 450 | + this._panelElements.heading,
|
|
| 451 | + "tor-circuit-panel-heading",
|
|
| 452 | 452 | // Only shorten the onion domain if it has no alias.
|
| 453 | - [TorUIUtils.shortenOnionAddress(domain)]
|
|
| 453 | + { host: TorUIUtils.shortenOnionAddress(domain) }
|
|
| 454 | 454 | );
|
| 455 | 455 | |
| 456 | 456 | if (onionAlias) {
|
| 457 | - this._panelElements.aliasLink.textContent =
|
|
| 458 | - TorUIUtils.shortenOnionAddress(onionAlias);
|
|
| 459 | 457 | if (scheme === "http" || scheme === "https") {
|
| 460 | 458 | // We assume the same scheme as the current page for the alias, which we
|
| 461 | 459 | // expect to be either http or https.
|
| 462 | 460 | // NOTE: The href property is partially presentational so that the link
|
| 463 | 461 | // location appears on hover.
|
| 464 | - this._panelElements.aliasLink.href = `${scheme}://${onionAlias}`;
|
|
| 462 | + // NOTE: The href attribute should be copied to any new instances of
|
|
| 463 | + // .tor-circuit-alias-link (with data-l10n-name="alias-link") when the
|
|
| 464 | + // parent _panelElements.aliasLabel gets re-translated.
|
|
| 465 | + this._aliasLink.href = `${scheme}://${onionAlias}`;
|
|
| 465 | 466 | } else {
|
| 466 | - this._panelElements.aliasLink.removeAttribute("href");
|
|
| 467 | + this._aliasLink.removeAttribute("href");
|
|
| 467 | 468 | }
|
| 469 | + document.l10n.setAttributes(
|
|
| 470 | + this._panelElements.aliasLabel,
|
|
| 471 | + "tor-circuit-panel-alias",
|
|
| 472 | + { alias: TorUIUtils.shortenOnionAddress(onionAlias) }
|
|
| 473 | + );
|
|
| 468 | 474 | this._showPanelElement(this._panelElements.alias, true);
|
| 469 | 475 | } else {
|
| 470 | 476 | this._showPanelElement(this._panelElements.alias, false);
|
| ... | ... | @@ -485,20 +491,40 @@ var gTorCircuitPanel = { |
| 485 | 491 | * @param {string} domain - The domain to show for the last node.
|
| 486 | 492 | */
|
| 487 | 493 | _updateBody(nodes, domain) {
|
| 488 | - // Clean up old items.
|
|
| 489 | - // NOTE: We do not expect focus within a removed node.
|
|
| 490 | - for (const nodeItem of this._nodeItems) {
|
|
| 491 | - nodeItem.remove();
|
|
| 492 | - }
|
|
| 494 | + // NOTE: Rather than re-creating the <li> nodes from scratch, we prefer
|
|
| 495 | + // updating existing <li> nodes so that the display does not "flicker" in
|
|
| 496 | + // width as we wait for Fluent DOM to fill the nodes with text content. I.e.
|
|
| 497 | + // the existing node and text will remain in place, occupying the same
|
|
| 498 | + // width, up until it is replaced by Fluent DOM.
|
|
| 499 | + for (let index = 0; index < nodes.length; index++) {
|
|
| 500 | + if (index >= this._nodeItems.length) {
|
|
| 501 | + const newItem =
|
|
| 502 | + this._nodeItemTemplate.content.children[0].cloneNode(true);
|
|
| 503 | + const flagEl = newItem.querySelector(".tor-circuit-region-flag");
|
|
| 504 | + // Hide region flag whenever the flag src does not exist.
|
|
| 505 | + flagEl.addEventListener("error", () => {
|
|
| 506 | + flagEl.classList.add("no-region-flag-src");
|
|
| 507 | + flagEl.removeAttribute("src");
|
|
| 508 | + });
|
|
| 509 | + this._panelElements.list.insertBefore(
|
|
| 510 | + newItem,
|
|
| 511 | + this._panelElements.relaysItem
|
|
| 512 | + );
|
|
| 493 | 513 | |
| 494 | - this._nodeItems = nodes.map((nodeData, index) => {
|
|
| 495 | - const nodeItem = this._createCircuitNodeItem(nodeData, index === 0);
|
|
| 496 | - this._panelElements.list.insertBefore(
|
|
| 497 | - nodeItem,
|
|
| 498 | - this._panelElements.relaysItem
|
|
| 514 | + this._nodeItems.push(newItem);
|
|
| 515 | + }
|
|
| 516 | + this._updateCircuitNodeItem(
|
|
| 517 | + this._nodeItems[index],
|
|
| 518 | + nodes[index],
|
|
| 519 | + index === 0
|
|
| 499 | 520 | );
|
| 500 | - return nodeItem;
|
|
| 501 | - });
|
|
| 521 | + }
|
|
| 522 | + |
|
| 523 | + // Remove excess items.
|
|
| 524 | + // NOTE: We do not expect focus within a removed node.
|
|
| 525 | + while (nodes.length < this._nodeItems.length) {
|
|
| 526 | + this._nodeItems.pop().remove();
|
|
| 527 | + }
|
|
| 502 | 528 | |
| 503 | 529 | this._showPanelElement(
|
| 504 | 530 | this._panelElements.relaysItem,
|
| ... | ... | @@ -511,40 +537,49 @@ var gTorCircuitPanel = { |
| 511 | 537 | |
| 512 | 538 | // Button description text, depending on whether our first node was a
|
| 513 | 539 | // bridge, or otherwise a guard.
|
| 514 | - this._panelElements.newCircuitDescription.value = this._getString(
|
|
| 540 | + document.l10n.setAttributes(
|
|
| 541 | + this._panelElements.newCircuitDescription,
|
|
| 515 | 542 | nodes[0].bridgeType === null
|
| 516 | - ? "torbutton.circuit_display.new-circuit-guard-description"
|
|
| 517 | - : "torbutton.circuit_display.new-circuit-bridge-description"
|
|
| 543 | + ? "tor-circuit-panel-new-button-description-guard"
|
|
| 544 | + : "tor-circuit-panel-new-button-description-bridge"
|
|
| 518 | 545 | );
|
| 519 | 546 | },
|
| 520 | 547 | |
| 521 | 548 | /**
|
| 522 | - * Create a node item for the given circuit node data.
|
|
| 549 | + * Update a node item for the given circuit node data.
|
|
| 523 | 550 | *
|
| 551 | + * @param {Element} nodeItem - The item to update.
|
|
| 524 | 552 | * @param {NodeData} node - The circuit node data to create an item for.
|
| 525 | 553 | * @param {bool} isCircuitStart - Whether this is the first node in the
|
| 526 | 554 | * circuit.
|
| 527 | 555 | */
|
| 528 | - _createCircuitNodeItem(node, isCircuitStart) {
|
|
| 529 | - let nodeName;
|
|
| 530 | - // We do not show a flag for bridge nodes.
|
|
| 531 | - let regionCode = null;
|
|
| 556 | + _updateCircuitNodeItem(nodeItem, node, isCircuitStart) {
|
|
| 557 | + const nameEl = nodeItem.querySelector(".tor-circuit-node-name");
|
|
| 558 | + let flagSrc = null;
|
|
| 559 | + |
|
| 532 | 560 | if (node.bridgeType === null) {
|
| 533 | - regionCode = node.regionCode;
|
|
| 534 | - if (!regionCode) {
|
|
| 535 | - nodeName = this._getString("torbutton.circuit_display.unknown_region");
|
|
| 536 | - } else {
|
|
| 537 | - nodeName = Services.intl.getRegionDisplayNames(undefined, [
|
|
| 538 | - regionCode,
|
|
| 539 | - ])[0];
|
|
| 540 | - }
|
|
| 561 | + const regionCode = node.regionCode;
|
|
| 562 | + flagSrc = this._regionFlagSrc(regionCode);
|
|
| 563 | + |
|
| 564 | + const regionName = regionCode
|
|
| 565 | + ? Services.intl.getRegionDisplayNames(undefined, [regionCode])[0]
|
|
| 566 | + : this._unknownRegionName;
|
|
| 567 | + |
|
| 541 | 568 | if (isCircuitStart) {
|
| 542 | - nodeName = this._getString(
|
|
| 543 | - "torbutton.circuit_display.region-guard-node",
|
|
| 544 | - [nodeName]
|
|
| 569 | + document.l10n.setAttributes(
|
|
| 570 | + nameEl,
|
|
| 571 | + "tor-circuit-panel-node-region-guard",
|
|
| 572 | + { region: regionName }
|
|
| 545 | 573 | );
|
| 574 | + } else {
|
|
| 575 | + // Set the text content directly, rather than using Fluent.
|
|
| 576 | + nameEl.removeAttribute("data-l10n-id");
|
|
| 577 | + nameEl.removeAttribute("data-l10n-args");
|
|
| 578 | + nameEl.textContent = regionName;
|
|
| 546 | 579 | }
|
| 547 | 580 | } else {
|
| 581 | + // Do not show a flag for bridges.
|
|
| 582 | + |
|
| 548 | 583 | let bridgeType = node.bridgeType;
|
| 549 | 584 | if (bridgeType === "meek_lite") {
|
| 550 | 585 | bridgeType = "meek";
|
| ... | ... | @@ -552,55 +587,72 @@ var gTorCircuitPanel = { |
| 552 | 587 | bridgeType = "";
|
| 553 | 588 | }
|
| 554 | 589 | if (bridgeType) {
|
| 555 | - nodeName = this._getString(
|
|
| 556 | - "torbutton.circuit_display.tor_typed_bridge",
|
|
| 557 | - [bridgeType]
|
|
| 590 | + document.l10n.setAttributes(
|
|
| 591 | + nameEl,
|
|
| 592 | + "tor-circuit-panel-node-typed-bridge",
|
|
| 593 | + { "bridge-type": bridgeType }
|
|
| 558 | 594 | );
|
| 559 | 595 | } else {
|
| 560 | - nodeName = this._getString("torbutton.circuit_display.tor_bridge");
|
|
| 596 | + document.l10n.setAttributes(nameEl, "tor-circuit-panel-node-bridge");
|
|
| 561 | 597 | }
|
| 562 | 598 | }
|
| 563 | - const nodeItem = document.createElement("li");
|
|
| 564 | - nodeItem.classList.add("tor-circuit-node-item");
|
|
| 565 | - |
|
| 566 | - const regionFlagEl = this._regionFlag(regionCode);
|
|
| 567 | - if (regionFlagEl) {
|
|
| 568 | - nodeItem.append(regionFlagEl);
|
|
| 599 | + const flagEl = nodeItem.querySelector(".tor-circuit-region-flag");
|
|
| 600 | + flagEl.classList.toggle("no-region-flag-src", !flagSrc);
|
|
| 601 | + if (flagSrc) {
|
|
| 602 | + flagEl.setAttribute("src", flagSrc);
|
|
| 603 | + } else {
|
|
| 604 | + flagEl.removeAttribute("src");
|
|
| 569 | 605 | }
|
| 570 | 606 | |
| 571 | - // Add whitespace after name for the addresses.
|
|
| 572 | - nodeItem.append(nodeName + " ");
|
|
| 573 | - |
|
| 574 | - if (node.ipAddrs) {
|
|
| 575 | - const addressesEl = document.createElement("span");
|
|
| 576 | - addressesEl.classList.add("tor-circuit-addresses");
|
|
| 577 | - let firstAddr = true;
|
|
| 578 | - for (const ip of node.ipAddrs) {
|
|
| 579 | - if (firstAddr) {
|
|
| 580 | - firstAddr = false;
|
|
| 581 | - } else {
|
|
| 582 | - addressesEl.append(", ");
|
|
| 583 | - }
|
|
| 584 | - // We use a <code> element to give screen readers a hint that
|
|
| 585 | - // punctuation is different for IP addresses.
|
|
| 586 | - const ipEl = document.createElement("code");
|
|
| 587 | - // TODO: Current HTML-aam 1.0 specs map the <code> element to the "code"
|
|
| 588 | - // role.
|
|
| 589 | - // However, mozilla-central commented out this mapping in
|
|
| 590 | - // accessible/base/HTMLMarkupMap.h because the HTML-aam specs at the
|
|
| 591 | - // time did not do this.
|
|
| 592 | - // See hg.mozilla.org/mozilla-central/rev/51eebe7d6199#l2.12
|
|
| 593 | - // For now we explicitly add the role="code", but once this is fixed
|
|
| 594 | - // from mozilla-central we should remove this.
|
|
| 595 | - ipEl.setAttribute("role", "code");
|
|
| 596 | - ipEl.classList.add("tor-circuit-ip-address");
|
|
| 597 | - ipEl.textContent = ip;
|
|
| 598 | - addressesEl.append(ipEl);
|
|
| 607 | + const addressesEl = nodeItem.querySelector(".tor-circuit-addresses");
|
|
| 608 | + // Empty children.
|
|
| 609 | + addressesEl.replaceChildren();
|
|
| 610 | + let firstAddr = true;
|
|
| 611 | + for (const ip of node.ipAddrs) {
|
|
| 612 | + if (firstAddr) {
|
|
| 613 | + firstAddr = false;
|
|
| 614 | + } else {
|
|
| 615 | + addressesEl.append(", ");
|
|
| 599 | 616 | }
|
| 600 | - nodeItem.append(addressesEl);
|
|
| 617 | + const ipEl = document.createElement("code");
|
|
| 618 | + // TODO: Current HTML-aam 1.0 specs map the <code> element to the "code"
|
|
| 619 | + // role.
|
|
| 620 | + // However, mozilla-central commented out this mapping in
|
|
| 621 | + // accessible/base/HTMLMarkupMap.h because the HTML-aam specs at the
|
|
| 622 | + // time did not do this.
|
|
| 623 | + // See hg.mozilla.org/mozilla-central/rev/51eebe7d6199#l2.12
|
|
| 624 | + //
|
|
| 625 | + // This was updated in mozilla bug 1834931, for ESR 128
|
|
| 626 | + //
|
|
| 627 | + // For now we explicitly add the role="code", but once this is fixed
|
|
| 628 | + // from mozilla-central we should remove this.
|
|
| 629 | + ipEl.setAttribute("role", "code");
|
|
| 630 | + ipEl.classList.add("tor-circuit-ip-address");
|
|
| 631 | + ipEl.textContent = ip;
|
|
| 632 | + addressesEl.append(ipEl);
|
|
| 601 | 633 | }
|
| 634 | + },
|
|
| 635 | + |
|
| 636 | + /**
|
|
| 637 | + * The string to use for unknown region names.
|
|
| 638 | + *
|
|
| 639 | + * Will be updated to match the current locale.
|
|
| 640 | + *
|
|
| 641 | + * @type {string}
|
|
| 642 | + */
|
|
| 643 | + _unknownRegionName: "Unknown region",
|
|
| 602 | 644 | |
| 603 | - return nodeItem;
|
|
| 645 | + /**
|
|
| 646 | + * Update the name for regions to match the current locale.
|
|
| 647 | + */
|
|
| 648 | + _localeChanged() {
|
|
| 649 | + document.l10n
|
|
| 650 | + .formatValue("tor-circuit-panel-node-unknown-region")
|
|
| 651 | + .then(name => {
|
|
| 652 | + this._unknownRegionName = name;
|
|
| 653 | + // Update the panel for the new region names, if it is shown.
|
|
| 654 | + this._updateCircuitPanel();
|
|
| 655 | + });
|
|
| 604 | 656 | },
|
| 605 | 657 | |
| 606 | 658 | /**
|
| ... | ... | @@ -609,9 +661,9 @@ var gTorCircuitPanel = { |
| 609 | 661 | * @param {string?} regionCode - The code to convert. It should be an upper
|
| 610 | 662 | * case 2-letter BCP47 Region subtag to be converted into a flag.
|
| 611 | 663 | *
|
| 612 | - * @returns {HTMLImgElement?} The emoji flag img, or null if there is no flag.
|
|
| 664 | + * @returns {src?} The emoji flag img src, or null if there is no flag.
|
|
| 613 | 665 | */
|
| 614 | - _regionFlag(regionCode) {
|
|
| 666 | + _regionFlagSrc(regionCode) {
|
|
| 615 | 667 | if (!regionCode?.match(/^[A-Z]{2}$/)) {
|
| 616 | 668 | return null;
|
| 617 | 669 | }
|
| ... | ... | @@ -624,20 +676,7 @@ var gTorCircuitPanel = { |
| 624 | 676 | .map(cp => cp.toString(16))
|
| 625 | 677 | .join("-");
|
| 626 | 678 | |
| 627 | - const flagEl = document.createElement("img");
|
|
| 628 | - // Decorative.
|
|
| 629 | - flagEl.alt = "";
|
|
| 630 | - flagEl.classList.add("tor-circuit-region-flag");
|
|
| 631 | - // Remove self if there is no matching flag found.
|
|
| 632 | - flagEl.addEventListener(
|
|
| 633 | - "error",
|
|
| 634 | - () => {
|
|
| 635 | - flagEl.classList.add("no-region-flag-src");
|
|
| 636 | - },
|
|
| 637 | - { once: true }
|
|
| 638 | - );
|
|
| 639 | - flagEl.src = `chrome://browser/content/tor-circuit-flags/${flagName}.svg`;
|
|
| 640 | - return flagEl;
|
|
| 679 | + return `chrome://browser/content/tor-circuit-flags/${flagName}.svg`;
|
|
| 641 | 680 | },
|
| 642 | 681 | |
| 643 | 682 | /**
|
| ... | ... | @@ -326,3 +326,63 @@ about-dialog-browser-license-link = Licensing Information |
| 326 | 326 | # "Tor" and "The Onion Logo" are trademark names, so should not be translated (not including the quote marks, which can be localized).
|
| 327 | 327 | # "The Tor Project, Inc." is an organisation name.
|
| 328 | 328 | about-dialog-trademark-statement = “Tor” and “The Onion Logo” are registered trademarks of The Tor Project, Inc.
|
| 329 | + |
|
| 330 | +## New tor circuit.
|
|
| 331 | + |
|
| 332 | +# Shown in the File menu.
|
|
| 333 | +# Uses title case for English (US).
|
|
| 334 | +menu-new-tor-circuit =
|
|
| 335 | + .label = New Tor Circuit for this Site
|
|
| 336 | + .accesskey = C
|
|
| 337 | + |
|
| 338 | +# Shown in the application menu (hamburger menu).
|
|
| 339 | +# Uses sentence case for English (US).
|
|
| 340 | +appmenuitem-new-tor-circuit =
|
|
| 341 | + .label = New Tor circuit for this site
|
|
| 342 | + |
|
| 343 | +# Toolbar button to trigger a new circuit, available through toolbar customization.
|
|
| 344 | +# Uses sentence case for English (US).
|
|
| 345 | +# ".label" is the accessible name, and is visible in the overflow menu and when
|
|
| 346 | +# customizing the toolbar.
|
|
| 347 | +# ".tooltiptext" will be identical to the label.
|
|
| 348 | +toolbar-new-tor-circuit =
|
|
| 349 | + .label = New Tor circuit for this site
|
|
| 350 | + .tooltiptext = { toolbar-new-tor-circuit.label }
|
|
| 351 | + |
|
| 352 | +## Tor circuit URL bar button.
|
|
| 353 | + |
|
| 354 | +# The tooltip also acts as the accessible name.
|
|
| 355 | +tor-circuit-urlbar-button =
|
|
| 356 | + .tooltiptext = Tor Circuit
|
|
| 357 | + |
|
| 358 | +## Tor circuit panel.
|
|
| 359 | + |
|
| 360 | +# $host (String) - The host name shown in the URL bar, potentially shortened.
|
|
| 361 | +tor-circuit-panel-heading = Circuit for { $host }
|
|
| 362 | +# Shown when the current address is a ".tor.onion" alias.
|
|
| 363 | +# $alias (String) - The alias onion address. This should be wrapped in '<a data-l10n-name="alias-link">' and '</a>', which will link to the corresponding address.
|
|
| 364 | +tor-circuit-panel-alias = Connected to <a data-l10n-name="alias-link">{ $alias }</a>
|
|
| 365 | + |
|
| 366 | +# Text just before the list of circuit nodes.
|
|
| 367 | +tor-circuit-panel-node-list-introduction = Tor Circuit
|
|
| 368 | +# First node in the list of circuit nodes. Refers to Tor Browser.
|
|
| 369 | +tor-circuit-panel-node-browser = This browser
|
|
| 370 | +# Represents a number of unknown relays that complete a connection to an ".onion" site.
|
|
| 371 | +tor-circuit-panel-node-onion-relays = Onion site relays
|
|
| 372 | +# Represents the bridge node used to connect to the Tor network.
|
|
| 373 | +# $bridge-type (String) - The name for the type of bridge used: meek, obfs4, snowflake, etc.
|
|
| 374 | +tor-circuit-panel-node-typed-bridge = Bridge: { $bridge-type }
|
|
| 375 | +# Represents the bridge node used to connect to the Tor network when the bridge type is unknown.
|
|
| 376 | +tor-circuit-panel-node-bridge = Bridge
|
|
| 377 | +# Represents the initial guard node used for a tor circuit.
|
|
| 378 | +# $region (String) - The region name for the guard node, already localized.
|
|
| 379 | +tor-circuit-panel-node-region-guard = { $region } (guard)
|
|
| 380 | +# Represents a circuit node with an unknown regional location.
|
|
| 381 | +tor-circuit-panel-node-unknown-region = Unknown region
|
|
| 382 | + |
|
| 383 | +# Uses sentence case for English (US).
|
|
| 384 | +tor-circuit-panel-new-button = New Tor circuit for this site
|
|
| 385 | +# Shown when the first node in the circuit is a guard node, rather than a bridge.
|
|
| 386 | +tor-circuit-panel-new-button-description-guard = Your guard node may not change
|
|
| 387 | +# Shown when the first node in the circuit is a bridge node.
|
|
| 388 | +tor-circuit-panel-new-button-description-bridge = Your bridge may not change |
| ... | ... | @@ -3,12 +3,6 @@ |
| 3 | 3 | - License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 4 | 4 | - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
| 5 | 5 | |
| 6 | -<!ENTITY torbutton.context_menu.new_circuit "New Tor Circuit for this Site">
|
|
| 7 | -<!ENTITY torbutton.context_menu.new_circuit_sentence_case "New Tor circuit for this site">
|
|
| 8 | -<!ENTITY torbutton.context_menu.new_circuit_key "C">
|
|
| 9 | - |
|
| 10 | -<!ENTITY torbutton.circuit_display.title "Tor Circuit">
|
|
| 11 | - |
|
| 12 | 6 | <!-- Onion services strings. Strings are kept here for ease of translation. -->
|
| 13 | 7 | <!ENTITY torbutton.onionServices.authPrompt.tooltip "Open onion service client authentication prompt">
|
| 14 | 8 | <!ENTITY torbutton.onionServices.authPrompt.persistCheckboxLabel "Remember this key"> |
| ... | ... | @@ -3,25 +3,6 @@ |
| 3 | 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this
|
| 4 | 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
| 5 | 5 | |
| 6 | -# Circuit display
|
|
| 7 | -# LOCALIZATION NOTE: %S will be the host name shown in the URL bar.
|
|
| 8 | -torbutton.circuit_display.heading = Circuit for %S
|
|
| 9 | -# LOCALIZATION NOTE: %S will be the alias onion address.
|
|
| 10 | -torbutton.circuit_display.connected-to-alias = Connected to %S
|
|
| 11 | -torbutton.circuit_display.this_browser = This browser
|
|
| 12 | -torbutton.circuit_display.onion-site-relays = Onion site relays
|
|
| 13 | -# LOCALIZATION NOTE: %S will be the bridge type name (meek, obfs4, snowflake,
|
|
| 14 | -# etc).
|
|
| 15 | -torbutton.circuit_display.tor_typed_bridge = Bridge: %S
|
|
| 16 | -# LOCALIZATION NOTE: Used when the bridge type is unknown.
|
|
| 17 | -torbutton.circuit_display.tor_bridge = Bridge
|
|
| 18 | -# LOCALIZATION NOTE: Used when a circuit node's regional location is unknown.
|
|
| 19 | -torbutton.circuit_display.unknown_region = Unknown region
|
|
| 20 | -# LOCALIZATION NOTE: %S will be the localized region name for the guard node.
|
|
| 21 | -torbutton.circuit_display.region-guard-node = %S (guard)
|
|
| 22 | -torbutton.circuit_display.new-circuit-guard-description = Your guard node may not change
|
|
| 23 | -torbutton.circuit_display.new-circuit-bridge-description = Your bridge may not change
|
|
| 24 | - |
|
| 25 | 6 | # Download pane warning
|
| 26 | 7 | torbutton.download.warning.title = Be careful opening downloads
|
| 27 | 8 | # %S will be a link to the Tails operating system website. With the content given by torbutton.download.warning.tails_brand_name
|
| ... | ... | @@ -253,16 +253,13 @@ class TorBrowserMigrationContext(MigrationContext): |
| 253 | 253 | if path not in self.localization_resources
|
| 254 | 254 | )
|
| 255 | 255 | |
| 256 | - def tb_get_transformed(self, target_path, transform_id):
|
|
| 256 | + def tb_get_transform(self, target_path, transform_id):
|
|
| 257 | 257 | """
|
| 258 | 258 | Find the transformation node with the given id for the given path.
|
| 259 | - |
|
| 260 | - The node will be evaluated (converted to regular fluent.ast) before it
|
|
| 261 | - is returned.
|
|
| 262 | 259 | """
|
| 263 | 260 | for node in self.transforms[target_path]:
|
| 264 | 261 | if node.id.name == transform_id:
|
| 265 | - return self.evaluate(node)
|
|
| 262 | + return node
|
|
| 266 | 263 | return None
|
| 267 | 264 | |
| 268 | 265 | def tb_get_reference_entry(self, target_path, entry_id):
|
| ... | ... | @@ -330,6 +327,21 @@ class TorBrowserMigrator: |
| 330 | 327 | |
| 331 | 328 | ctx = self._get_migration_context(locale, locale_dir)
|
| 332 | 329 | |
| 330 | + # NOTE: We do not use the existing ctx.serialize_changeset method.
|
|
| 331 | + # The problem with this approach was that it would re-shuffle the order
|
|
| 332 | + # of already existing strings to match the en-US locale.
|
|
| 333 | + # But Weblate currently does not preserve the order of translated
|
|
| 334 | + # strings: https://github.com/WeblateOrg/weblate/issues/11134
|
|
| 335 | + # so this created extra noise in the diff.
|
|
| 336 | + # Instead, we just always append transformations to the end of the
|
|
| 337 | + # existing file.
|
|
| 338 | + # Moreover, it would inject group comments into the translated files,
|
|
| 339 | + # which Weblate does not handle well. Instead, we just do not add any
|
|
| 340 | + # comments.
|
|
| 341 | + #
|
|
| 342 | + # In case we want to use it again in the future, here is a reference
|
|
| 343 | + # to how it works:
|
|
| 344 | + #
|
|
| 333 | 345 | # ctx.serialize_changeset expects a set of (path, identifier) of
|
| 334 | 346 | # localization resources that can be used to evaluate the
|
| 335 | 347 | # transformations.
|
| ... | ... | @@ -344,76 +356,115 @@ class TorBrowserMigrator: |
| 344 | 356 | # one step, so we want to fill the changeset with all required
|
| 345 | 357 | # (path, identifier) pairs found in the localization resources.
|
| 346 | 358 | |
| 347 | - # Choose the transforms that are required and available.
|
|
| 348 | - changeset = set()
|
|
| 349 | 359 | available_strings = ctx.tb_get_available_strings()
|
| 350 | - for (target_path, transform_id), dep_set in ctx.dependencies.items():
|
|
| 351 | - # ctx.dependencies is a dict of dependencies for all
|
|
| 352 | - # transformations
|
|
| 353 | - # { (target_path, transform_identifier): set(
|
|
| 354 | - # (localization_path, string_identifier),
|
|
| 355 | - # )}
|
|
| 356 | - #
|
|
| 357 | - # e.g. if we want to create a new fluent Message called
|
|
| 358 | - # "new-string1", and it uses "oldString1" from "old-file1.dtd"
|
|
| 359 | - # and "oldString2" from "old-file2.dtd". And "new-string2" using
|
|
| 360 | - # "oldString3" from "old-file2.dtd", it would be
|
|
| 361 | - # {
|
|
| 362 | - # ("new-file.ftl", "new-string1"): set(
|
|
| 363 | - # ("old-file1.dtd", "oldString1"),
|
|
| 364 | - # ("old-file2.dtd", "oldString2"),
|
|
| 365 | - # ),
|
|
| 366 | - # ("new-file.ftl", "new-string2"): set(
|
|
| 367 | - # ("old-file2.dtd", "oldString3"),
|
|
| 368 | - # ),
|
|
| 369 | - # }
|
|
| 370 | - can_transform = True
|
|
| 371 | - for dep in dep_set:
|
|
| 372 | - path, string_id = dep
|
|
| 373 | - if dep not in available_strings:
|
|
| 374 | - can_transform = False
|
|
| 375 | - self.logger.info(
|
|
| 376 | - f"Skipping transform {target_path}:{transform_id} for "
|
|
| 377 | - f"'{locale}' locale because it is missing the "
|
|
| 378 | - f"string {path}:{string_id}."
|
|
| 379 | - )
|
|
| 380 | - break
|
|
| 381 | - # Strings in legacy formats might have an entry in the file
|
|
| 382 | - # that is just a copy of the en-US strings.
|
|
| 383 | - # For these we want to check the weblate metadata to ensure
|
|
| 384 | - # it is a translated string.
|
|
| 385 | - if not path.endswith(
|
|
| 386 | - ".ftl"
|
|
| 387 | - ) and not self.weblate_metadata.is_translated(
|
|
| 388 | - os.path.join("en-US", path),
|
|
| 389 | - os.path.join(locale, path),
|
|
| 390 | - string_id,
|
|
| 391 | - ):
|
|
| 392 | - can_transform = False
|
|
| 360 | + wrote_file = False
|
|
| 361 | + errors = []
|
|
| 362 | + |
|
| 363 | + for target_path, reference in ctx.reference_resources.items():
|
|
| 364 | + translated_ids = [
|
|
| 365 | + entry.id.name
|
|
| 366 | + for entry in ctx.target_resources[target_path].body
|
|
| 367 | + if isinstance(entry, (ast.Message, ast.Term))
|
|
| 368 | + # NOTE: We're assuming that the Message and Term ids do not
|
|
| 369 | + # conflict with each other.
|
|
| 370 | + ]
|
|
| 371 | + new_entries = []
|
|
| 372 | + |
|
| 373 | + # Apply transfomations in the order they appear in the reference
|
|
| 374 | + # (en-US) file.
|
|
| 375 | + for entry in reference.body:
|
|
| 376 | + if not isinstance(entry, (ast.Message, ast.Term)):
|
|
| 377 | + continue
|
|
| 378 | + transform_id = entry.id.name
|
|
| 379 | + transform = ctx.tb_get_transform(target_path, transform_id)
|
|
| 380 | + if not transform:
|
|
| 381 | + # No transformation for this reference entry.
|
|
| 382 | + continue
|
|
| 383 | + |
|
| 384 | + if transform_id in translated_ids:
|
|
| 393 | 385 | self.logger.info(
|
| 394 | - f"Skipping transform {target_path}:{transform_id} for "
|
|
| 395 | - f"'{locale}' locale because the string "
|
|
| 396 | - f"{path}:{string_id} has not been translated on "
|
|
| 397 | - "weblate."
|
|
| 386 | + f"Skipping transform {target_path}:{transform_id} "
|
|
| 387 | + f"for '{locale}' locale because it already has a "
|
|
| 388 | + f"translation."
|
|
| 398 | 389 | )
|
| 399 | - break
|
|
| 400 | - if can_transform:
|
|
| 401 | - changeset.update(dep_set)
|
|
| 390 | + continue
|
|
| 402 | 391 | |
| 403 | - print("", file=sys.stderr)
|
|
| 404 | - wrote_file = False
|
|
| 405 | - errors = []
|
|
| 406 | - for path, fluent in ctx.serialize_changeset(changeset).items():
|
|
| 407 | - full_path = os.path.join(locale_dir, path)
|
|
| 392 | + # ctx.dependencies is a dict of dependencies for all
|
|
| 393 | + # transformations
|
|
| 394 | + # { (target_path, transform_identifier): set(
|
|
| 395 | + # (localization_path, string_identifier),
|
|
| 396 | + # )}
|
|
| 397 | + #
|
|
| 398 | + # e.g. if we want to create a new fluent Message called
|
|
| 399 | + # "new-string1", and it uses "oldString1" from "old-file1.dtd"
|
|
| 400 | + # and "oldString2" from "old-file2.dtd". And "new-string2" using
|
|
| 401 | + # "oldString3" from "old-file2.dtd", it would be
|
|
| 402 | + # {
|
|
| 403 | + # ("new-file.ftl", "new-string1"): set(
|
|
| 404 | + # ("old-file1.dtd", "oldString1"),
|
|
| 405 | + # ("old-file2.dtd", "oldString2"),
|
|
| 406 | + # ),
|
|
| 407 | + # ("new-file.ftl", "new-string2"): set(
|
|
| 408 | + # ("old-file2.dtd", "oldString3"),
|
|
| 409 | + # ),
|
|
| 410 | + # }
|
|
| 411 | + dep_set = ctx.dependencies[(target_path, transform_id)]
|
|
| 412 | + can_transform = True
|
|
| 413 | + for dep in dep_set:
|
|
| 414 | + path, string_id = dep
|
|
| 415 | + if dep not in available_strings:
|
|
| 416 | + can_transform = False
|
|
| 417 | + self.logger.info(
|
|
| 418 | + f"Skipping transform {target_path}:{transform_id} "
|
|
| 419 | + f"for '{locale}' locale because it is missing the "
|
|
| 420 | + f"string {path}:{string_id}."
|
|
| 421 | + )
|
|
| 422 | + break
|
|
| 423 | + # Strings in legacy formats might have an entry in the file
|
|
| 424 | + # that is just a copy of the en-US strings.
|
|
| 425 | + # For these we want to check the weblate metadata to ensure
|
|
| 426 | + # it is a translated string.
|
|
| 427 | + if not path.endswith(
|
|
| 428 | + ".ftl"
|
|
| 429 | + ) and not self.weblate_metadata.is_translated(
|
|
| 430 | + os.path.join("en-US", path),
|
|
| 431 | + os.path.join(locale, path),
|
|
| 432 | + string_id,
|
|
| 433 | + ):
|
|
| 434 | + can_transform = False
|
|
| 435 | + self.logger.info(
|
|
| 436 | + f"Skipping transform {target_path}:{transform_id} "
|
|
| 437 | + f"for '{locale}' locale because the string "
|
|
| 438 | + f"{path}:{string_id} has not been translated on "
|
|
| 439 | + "weblate."
|
|
| 440 | + )
|
|
| 441 | + break
|
|
| 442 | + if not can_transform:
|
|
| 443 | + continue
|
|
| 444 | + |
|
| 445 | + # Run the transformation.
|
|
| 446 | + new_entries.append(ctx.evaluate(transform))
|
|
| 447 | + |
|
| 448 | + if not new_entries:
|
|
| 449 | + continue
|
|
| 450 | + |
|
| 451 | + full_path = os.path.join(locale_dir, target_path)
|
|
| 452 | + print("", file=sys.stderr)
|
|
| 408 | 453 | self.logger.info(f"Writing to {full_path}")
|
| 409 | - with open(full_path, "w") as file:
|
|
| 410 | - file.write(fluent)
|
|
| 411 | - wrote_file = True
|
|
| 412 | 454 | |
| 455 | + # For Fluent we can just serialize the transformations and append
|
|
| 456 | + # them to the end of the existing file.
|
|
| 457 | + resource = ast.Resource(new_entries)
|
|
| 458 | + with open(full_path, "a") as file:
|
|
| 459 | + file.write(serialize(resource))
|
|
| 460 | + |
|
| 461 | + with open(full_path, "r") as file:
|
|
| 462 | + full_content = file.read()
|
|
| 463 | + wrote_file = True
|
|
| 413 | 464 | # Collect any fluent parsing errors from the newly written file.
|
| 414 | 465 | errors.extend(
|
| 415 | 466 | (full_path, message, line, sample)
|
| 416 | - for message, line, sample in self._fluent_errors(fluent)
|
|
| 467 | + for message, line, sample in self._fluent_errors(full_content)
|
|
| 417 | 468 | )
|
| 418 | 469 | |
| 419 | 470 | if not wrote_file:
|
| ... | ... | @@ -547,7 +598,7 @@ class TorBrowserMigrator: |
| 547 | 598 | have_error = True
|
| 548 | 599 | continue
|
| 549 | 600 | |
| 550 | - transformed = ctx.tb_get_transformed(target_path, transform_id)
|
|
| 601 | + transformed = ctx.evaluate(ctx.tb_get_transform(target_path, transform_id))
|
|
| 551 | 602 | reference_entry = ctx.tb_get_reference_entry(target_path, transform_id)
|
| 552 | 603 | if reference_entry is None:
|
| 553 | 604 | self.logger.error(
|
| 1 | +import fluent.syntax.ast as FTL
|
|
| 2 | +from fluent.migrate.helpers import VARIABLE_REFERENCE, transforms_from
|
|
| 3 | +from fluent.migrate.transforms import CONCAT, REPLACE
|
|
| 4 | + |
|
| 5 | + |
|
| 6 | +def migrate(ctx):
|
|
| 7 | + legacy_dtd = "torbutton.dtd"
|
|
| 8 | + legacy_properties = "torbutton.properties"
|
|
| 9 | + ctx.add_transforms(
|
|
| 10 | + "tor-browser.ftl",
|
|
| 11 | + "tor-browser.ftl",
|
|
| 12 | + transforms_from(
|
|
| 13 | + """
|
|
| 14 | +menu-new-tor-circuit =
|
|
| 15 | + .label = { COPY(dtd_path, "torbutton.context_menu.new_circuit") }
|
|
| 16 | + .accesskey = { COPY(dtd_path, "torbutton.context_menu.new_circuit_key") }
|
|
| 17 | +appmenuitem-new-tor-circuit =
|
|
| 18 | + .label = { COPY(dtd_path, "torbutton.context_menu.new_circuit_sentence_case") }
|
|
| 19 | +toolbar-new-tor-circuit =
|
|
| 20 | + .label = { COPY(dtd_path, "torbutton.context_menu.new_circuit_sentence_case") }
|
|
| 21 | + .tooltiptext = { toolbar-new-tor-circuit.label }
|
|
| 22 | + |
|
| 23 | +tor-circuit-urlbar-button =
|
|
| 24 | + .tooltiptext = { COPY(dtd_path, "torbutton.circuit_display.title") }
|
|
| 25 | + |
|
| 26 | +tor-circuit-panel-node-list-introduction = { COPY(dtd_path, "torbutton.circuit_display.title") }
|
|
| 27 | +tor-circuit-panel-node-browser = { COPY(path, "torbutton.circuit_display.this_browser") }
|
|
| 28 | +tor-circuit-panel-node-onion-relays = { COPY(path, "torbutton.circuit_display.onion-site-relays") }
|
|
| 29 | +tor-circuit-panel-node-bridge = { COPY(path, "torbutton.circuit_display.tor_bridge") }
|
|
| 30 | +tor-circuit-panel-node-unknown-region = { COPY(path, "torbutton.circuit_display.unknown_region") }
|
|
| 31 | + |
|
| 32 | +tor-circuit-panel-new-button = { COPY(dtd_path, "torbutton.context_menu.new_circuit_sentence_case") }
|
|
| 33 | +tor-circuit-panel-new-button-description-guard = { COPY(path, "torbutton.circuit_display.new-circuit-guard-description") }
|
|
| 34 | +tor-circuit-panel-new-button-description-bridge = { COPY(path, "torbutton.circuit_display.new-circuit-bridge-description") }
|
|
| 35 | +""",
|
|
| 36 | + dtd_path=legacy_dtd,
|
|
| 37 | + path=legacy_properties,
|
|
| 38 | + )
|
|
| 39 | + + [
|
|
| 40 | + # Replace "%S" with "{ $host }"
|
|
| 41 | + FTL.Message(
|
|
| 42 | + id=FTL.Identifier("tor-circuit-panel-heading"),
|
|
| 43 | + value=REPLACE(
|
|
| 44 | + legacy_properties,
|
|
| 45 | + "torbutton.circuit_display.heading",
|
|
| 46 | + {"%1$S": VARIABLE_REFERENCE("host")},
|
|
| 47 | + ),
|
|
| 48 | + ),
|
|
| 49 | + # Replace "%S" with "<a data-l10n-name="alias-link">{ $alias }</a>"
|
|
| 50 | + FTL.Message(
|
|
| 51 | + id=FTL.Identifier("tor-circuit-panel-alias"),
|
|
| 52 | + value=REPLACE(
|
|
| 53 | + legacy_properties,
|
|
| 54 | + "torbutton.circuit_display.connected-to-alias",
|
|
| 55 | + {
|
|
| 56 | + "%1$S": CONCAT(
|
|
| 57 | + FTL.TextElement('<a data-l10n-name="alias-link">'),
|
|
| 58 | + VARIABLE_REFERENCE("alias"),
|
|
| 59 | + FTL.TextElement("</a>"),
|
|
| 60 | + )
|
|
| 61 | + },
|
|
| 62 | + ),
|
|
| 63 | + ),
|
|
| 64 | + # Replace "%S" with "{ $region }"
|
|
| 65 | + FTL.Message(
|
|
| 66 | + id=FTL.Identifier("tor-circuit-panel-node-region-guard"),
|
|
| 67 | + value=REPLACE(
|
|
| 68 | + legacy_properties,
|
|
| 69 | + "torbutton.circuit_display.region-guard-node",
|
|
| 70 | + {"%1$S": VARIABLE_REFERENCE("region")},
|
|
| 71 | + ),
|
|
| 72 | + ),
|
|
| 73 | + # Replace "%S" with "{ $bridge-type }"
|
|
| 74 | + FTL.Message(
|
|
| 75 | + id=FTL.Identifier("tor-circuit-panel-node-typed-bridge"),
|
|
| 76 | + value=REPLACE(
|
|
| 77 | + legacy_properties,
|
|
| 78 | + "torbutton.circuit_display.tor_typed_bridge",
|
|
| 79 | + {"%1$S": VARIABLE_REFERENCE("bridge-type")},
|
|
| 80 | + ),
|
|
| 81 | + ),
|
|
| 82 | + ],
|
|
| 83 | + ) |