Skip to main content
Sign in
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • tor-browser-140.6.0esr-15.0-1
  • base-browser-140.6.0esr-15.0-1
  • tor-browser-146.0a1-16.0-1
  • base-browser-146.0a1-16.0-1
  • tor-browser-115.31.0esr-13.5-1
  • base-browser-145.0a1-16.0-1
  • tor-browser-145.0a1-16.0-1
  • base-browser-140.5.0esr-15.0-1
  • tor-browser-140.5.0esr-15.0-1
  • tor-browser-144.0a1-16.0-2
  • tor-browser-144.0a1-16.0-1
  • tor-browser-115.30.0esr-13.5-1
  • base-browser-140.4.0esr-15.0-1
  • tor-browser-140.4.0esr-15.0-1
  • tor-browser-115.29.0esr-13.5-1
  • base-browser-128.14.0esr-14.5-1
  • tor-browser-128.14.0esr-14.5-1
  • tor-browser-140.3.0esr-15.0-1
  • tor-browser-115.28.0esr-13.5-1
  • base-browser-140.3.0esr-15.0-1
  • tor-browser-143.0a1-16.0-2
  • tor-browser-143.0a1-16.0-1
  • tor-browser-140.2.0esr-15.0-1
  • base-browser-140.2.0esr-15.0-1
  • tor-browser-115.27.0esr-13.5-1
  • tor-browser-115.26.0esr-13.5-1
  • tor-browser-142.0a1-16.0-2
  • tor-browser-142.0a1-16.0-1
  • tor-browser-140.1.0esr-15.0-1
  • base-browser-140.1.0esr-15.0-1
  • base-browser-128.13.0esr-14.5-1
  • tor-browser-128.13.0esr-14.5-1
  • tor-browser-141.0a1-16.0-2
  • base-browser-141.0a1-16.0-2
  • tor-browser-141.0a1-16.0-1
  • tor-browser-140.0esr-15.0-1
  • base-browser-140.0esr-15.0-1
  • tor-browser-140.0a1-15.0-2
  • base-browser-140.0a1-15.0-2
  • tor-browser-128.12.0esr-14.5-1
  • base-browser-128.12.0esr-14.5-1
  • base-browser-141.0a1-16.0-1
  • tor-browser-115.25.0esr-13.5-1
  • base-browser-140.0a1-15.0-1
  • tor-browser-140.0a1-15.0-1
  • base-browser-115.25.0esr-13.5-1
  • base-browser-128.11.0esr-14.5-1
  • tor-browser-115.24.0esr-13.5-1
  • tor-browser-128.11.0esr-14.5-1
  • tor-browser-139.0a1-15.0-1
  • tor-browser-128.10.1esr-14.5-1
  • base-browser-128.10.1esr-14.5-1
  • tor-browser-115.23.1esr-13.5-1
  • base-browser-128.10.0esr-14.5-1
  • tor-browser-138.0a1-15.0-1
  • tor-browser-128.10.0esr-14.5-1
  • tor-browser-137.0a1-15.0-1
  • tor-browser-136.0a1-15.0-1
  • tor-browser-135.0a1-15.0-1
  • tor-browser-134.0a1-15.0-2
  • tor-browser-134.0a1-15.0-1
  • tor-browser-115.23.0esr-13.5-1
  • tor-browser-133.0a1-15.0-2
  • tor-browser-128.9.0esr-14.5-1
  • base-browser-128.9.0esr-14.5-1
  • tor-browser-133.0a1-15.0-1
  • tor-browser-115.22.0esr-13.5-1
  • base-browser-128.9.0esr-14.0-2
  • tor-browser-128.9.0esr-14.0-2
  • tor-browser-128.8.0esr-14.5-1
  • tor-browser-115.21.0esr-13.5-1
  • tor-browser-128.8.0esr-14.0-1
  • tor-browser-128.9.0esr-14.0-1
  • base-browser-128.9.0esr-14.0-1
  • base-browser-128.8.0esr-14.5-1
  • tor-browser-132.0a1-15.0-2
  • tor-browser-132.0a1-15.0-1
  • base-browser-128.8.0esr-14.0-1
  • tor-browser-128.7.0esr-14.5-1
  • base-browser-128.7.0esr-14.5-1
  • tor-browser-131.0a1-15.0-1
  • base-browser-131.0a1-15.0-1
  • tor-browser-130.0a1-15.0-1
  • base-browser-130.0a1-15.0-1
  • base-browser-115.21.0esr-13.5-1
  • tor-browser-115.20.0esr-13.5-1
  • tor-browser-129.0a1-15.0-2
  • tor-browser-129.0a1-15.0-1
  • tor-browser-128.7.0esr-14.0-1
  • base-browser-128.7.0esr-14.0-1
  • tor-browser-128.6.0esr-14.5-1
  • base-browser-128.6.0esr-14.0-1
  • tor-browser-128.6.0esr-14.0-1
  • base-browser-128.6.0esr-14.5-1
  • tor-browser-115.19.0esr-13.5-1
  • base-browser-115.19.0esr-13.5-1
  • tor-browser-128.5.0esr-14.5-2
  • base-browser-128.5.0esr-14.5-2
  • tor-browser-128.5.0esr-14.5-1
  • base-browser-128.5.0esr-14.5-1
  • B2G_1_4_20140317_MERGEDAY
  • B2G_2_0_20140609_MERGEDAY
  • B2G_2_1_20140902_MERGEDAY
  • FIREFOX_102_0_1_RELEASE
  • FIREFOX_102_10_0esr_BUILD1
  • FIREFOX_102_11_0esr_BUILD2
  • FIREFOX_102_12_0esr_BUILD1
  • FIREFOX_102_13_0esr_BUILD1
  • FIREFOX_102_14_0esr_BUILD1
  • FIREFOX_102_15_0esr_BUILD1
  • FIREFOX_102_15_1esr_BUILD1
  • FIREFOX_102_2_0esr_BUILD2
  • FIREFOX_102_3_0esr_RELEASE
  • FIREFOX_102_4_0esr_BUILD1
  • FIREFOX_102_5_0esr_RELEASE
  • FIREFOX_102_6_0esr_BUILD1
  • FIREFOX_102_7_0esr_BUILD1
  • FIREFOX_102_8_0esr_BUILD1
  • FIREFOX_102_9_0esr_BUILD1
  • FIREFOX_115_0_2esr_RELEASE
  • FIREFOX_115_0b3_RELEASE
  • FIREFOX_115_0b4_RELEASE
  • FIREFOX_115_0b5_RELEASE
  • FIREFOX_115_10_0esr_BUILD1
  • FIREFOX_115_11_0esr_BUILD1
  • FIREFOX_115_12_0esr_BUILD1
  • FIREFOX_115_13_0esr_BUILD3
  • FIREFOX_115_14_0esr_BUILD1
  • FIREFOX_115_15_0esr_BUILD1
  • FIREFOX_115_16_0esr_BUILD1
  • FIREFOX_115_17_0esr_BUILD1
  • FIREFOX_115_18_0esr_BUILD1
  • FIREFOX_115_19_0esr_BUILD1
  • FIREFOX_115_1_0esr_BUILD1
  • FIREFOX_115_20_0esr_BUILD1
  • FIREFOX_115_21_0esr_BUILD1
  • FIREFOX_115_22_0esr_BUILD1
  • FIREFOX_115_22_0esr_BUILD2
  • FIREFOX_115_23_0esr_BUILD1
  • FIREFOX_115_23_1esr_BUILD1
  • FIREFOX_115_24_0esr_BUILD1
  • FIREFOX_115_25_0esr_BUILD1
  • FIREFOX_115_26_0esr_BUILD1
  • FIREFOX_115_27_0esr_BUILD1
  • FIREFOX_115_28_0esr_BUILD1
  • FIREFOX_115_2_0esr_BUILD1
  • FIREFOX_115_2_1esr_BUILD1
  • FIREFOX_115_30_0esr_BUILD1
  • FIREFOX_115_31_0esr_BUILD1
  • FIREFOX_115_3_0esr_BUILD1
  • FIREFOX_115_3_1esr_RELEASE
  • FIREFOX_115_4_0esr_BUILD1
  • FIREFOX_115_5_0esr_BUILD1
  • FIREFOX_115_6_0esr_BUILD1
  • FIREFOX_115_7_0esr_BUILD1
  • FIREFOX_115_8_0esr_BUILD1
  • FIREFOX_115_9_0esr_BUILD1
  • FIREFOX_115_9_1esr_BUILD1
  • FIREFOX_116_0_3_RELEASE
  • FIREFOX_117_0_RELEASE
  • FIREFOX_118_0_RELEASE
  • FIREFOX_119_0_RELEASE
  • FIREFOX_120_0_RELEASE
  • FIREFOX_121_0_RELEASE
  • FIREFOX_122_0_RELEASE
  • FIREFOX_123_0_RELEASE
  • FIREFOX_124_0_RELEASE
  • FIREFOX_125_0_BUILD1
  • FIREFOX_126_0_BUILD1
  • FIREFOX_127_0b1_RELEASE
  • FIREFOX_128_0b1_BUILD1
  • FIREFOX_128_0esr_RELEASE
  • FIREFOX_128_10_0esr_BUILD1
  • FIREFOX_128_11_0esr_BUILD1
  • FIREFOX_128_12_0esr_BUILD1
  • FIREFOX_128_13_0esr_BUILD1
  • FIREFOX_128_14_0esr_BUILD1
  • FIREFOX_128_1_0esr_BUILD1
  • FIREFOX_128_2_0esr_BUILD1
  • FIREFOX_128_3_0esr_BUILD1
  • FIREFOX_128_4_0esr_BUILD1
  • FIREFOX_128_5_0esr_BUILD1
  • FIREFOX_128_6_0esr_BUILD1
  • FIREFOX_128_7_0esr_BUILD1
  • FIREFOX_128_8_0esr_BUILD1
  • FIREFOX_128_9_0esr_BUILD1
  • FIREFOX_128_9_0esr_BUILD2
  • FIREFOX_140_0esr_RELEASE
  • FIREFOX_140_1_0esr_BUILD1
  • FIREFOX_140_2_0esr_BUILD1
  • FIREFOX_140_3_0esr_BUILD1
  • FIREFOX_140_4_0esr_BUILD1
  • FIREFOX_140_5_0esr_BUILD1
  • FIREFOX_140_6_0esr_BUILD1
  • FIREFOX_91_10_0esr_BUILD1
  • FIREFOX_91_11_0esr_BUILD1
  • FIREFOX_91_12_0esr_BUILD1
  • FIREFOX_91_13_0esr_BUILD1
  • FIREFOX_91_7_0esr_BUILD2
  • FIREFOX_91_8_0esr_BUILD1
200 results

Target

Select target project
  • Georg Koppen / Tor Browser
  • peterstory / Tor Browser
  • sanketh / Tor Browser
  • Alex Catarineu / Tor Browser
  • Matthew Finkel / Tor Browser
  • boklm / Tor Browser
  • Dan Ballard / Tor Browser
  • Fabrizio / Tor Browser
  • victorvw / Tor Browser
  • aguestuser / Tor Browser
  • WofWca / Tor Browser
  • p13dz / Tor Browser
  • mwolfe / Tor Browser
  • The Tor Project / Applications / Tor Browser
  • Kathleen Brade / Tor Browser - brade
  • Pier Angelo Vendrame / Tor Browser
  • ma1 / Tor Browser
  • JeremyRand / Tor Browser
  • henry / Tor Browser
  • Marco Simonelli / Tor Browser
20 results
Select Git revision
  • tor-browser-140.6.0esr-15.0-1
  • base-browser-140.6.0esr-15.0-1
  • tor-browser-146.0a1-16.0-1
  • base-browser-146.0a1-16.0-1
  • tor-browser-115.31.0esr-13.5-1
  • base-browser-145.0a1-16.0-1
  • tor-browser-145.0a1-16.0-1
  • base-browser-140.5.0esr-15.0-1
  • tor-browser-140.5.0esr-15.0-1
  • tor-browser-144.0a1-16.0-2
  • tor-browser-144.0a1-16.0-1
  • tor-browser-115.30.0esr-13.5-1
  • base-browser-140.4.0esr-15.0-1
  • tor-browser-140.4.0esr-15.0-1
  • tor-browser-115.29.0esr-13.5-1
  • base-browser-128.14.0esr-14.5-1
  • tor-browser-128.14.0esr-14.5-1
  • tor-browser-140.3.0esr-15.0-1
  • tor-browser-115.28.0esr-13.5-1
  • base-browser-140.3.0esr-15.0-1
  • tor-browser-143.0a1-16.0-2
  • tor-browser-143.0a1-16.0-1
  • tor-browser-140.2.0esr-15.0-1
  • base-browser-140.2.0esr-15.0-1
  • tor-browser-115.27.0esr-13.5-1
  • tor-browser-115.26.0esr-13.5-1
  • tor-browser-142.0a1-16.0-2
  • tor-browser-142.0a1-16.0-1
  • tor-browser-140.1.0esr-15.0-1
  • base-browser-140.1.0esr-15.0-1
  • base-browser-128.13.0esr-14.5-1
  • tor-browser-128.13.0esr-14.5-1
  • tor-browser-141.0a1-16.0-2
  • base-browser-141.0a1-16.0-2
  • tor-browser-141.0a1-16.0-1
  • tor-browser-140.0esr-15.0-1
  • base-browser-140.0esr-15.0-1
  • tor-browser-140.0a1-15.0-2
  • base-browser-140.0a1-15.0-2
  • tor-browser-128.12.0esr-14.5-1
  • base-browser-128.12.0esr-14.5-1
  • base-browser-141.0a1-16.0-1
  • tor-browser-115.25.0esr-13.5-1
  • base-browser-140.0a1-15.0-1
  • tor-browser-140.0a1-15.0-1
  • base-browser-115.25.0esr-13.5-1
  • base-browser-128.11.0esr-14.5-1
  • tor-browser-115.24.0esr-13.5-1
  • tor-browser-128.11.0esr-14.5-1
  • tor-browser-139.0a1-15.0-1
  • tor-browser-128.10.1esr-14.5-1
  • base-browser-128.10.1esr-14.5-1
  • tor-browser-115.23.1esr-13.5-1
  • base-browser-128.10.0esr-14.5-1
  • tor-browser-138.0a1-15.0-1
  • tor-browser-128.10.0esr-14.5-1
  • tor-browser-137.0a1-15.0-1
  • tor-browser-136.0a1-15.0-1
  • tor-browser-135.0a1-15.0-1
  • tor-browser-134.0a1-15.0-2
  • tor-browser-134.0a1-15.0-1
  • tor-browser-115.23.0esr-13.5-1
  • tor-browser-133.0a1-15.0-2
  • tor-browser-128.9.0esr-14.5-1
  • base-browser-128.9.0esr-14.5-1
  • tor-browser-133.0a1-15.0-1
  • tor-browser-115.22.0esr-13.5-1
  • base-browser-128.9.0esr-14.0-2
  • tor-browser-128.9.0esr-14.0-2
  • tor-browser-128.8.0esr-14.5-1
  • tor-browser-115.21.0esr-13.5-1
  • tor-browser-128.8.0esr-14.0-1
  • tor-browser-128.9.0esr-14.0-1
  • base-browser-128.9.0esr-14.0-1
  • base-browser-128.8.0esr-14.5-1
  • tor-browser-132.0a1-15.0-2
  • tor-browser-132.0a1-15.0-1
  • base-browser-128.8.0esr-14.0-1
  • tor-browser-128.7.0esr-14.5-1
  • base-browser-128.7.0esr-14.5-1
  • tor-browser-131.0a1-15.0-1
  • base-browser-131.0a1-15.0-1
  • tor-browser-130.0a1-15.0-1
  • base-browser-130.0a1-15.0-1
  • base-browser-115.21.0esr-13.5-1
  • tor-browser-115.20.0esr-13.5-1
  • tor-browser-129.0a1-15.0-2
  • tor-browser-129.0a1-15.0-1
  • tor-browser-128.7.0esr-14.0-1
  • base-browser-128.7.0esr-14.0-1
  • tor-browser-128.6.0esr-14.5-1
  • base-browser-128.6.0esr-14.0-1
  • tor-browser-128.6.0esr-14.0-1
  • base-browser-128.6.0esr-14.5-1
  • tor-browser-115.19.0esr-13.5-1
  • base-browser-115.19.0esr-13.5-1
  • tor-browser-128.5.0esr-14.5-2
  • base-browser-128.5.0esr-14.5-2
  • tor-browser-128.5.0esr-14.5-1
  • base-browser-128.5.0esr-14.5-1
  • B2G_1_4_20140317_MERGEDAY
  • B2G_2_0_20140609_MERGEDAY
  • B2G_2_1_20140902_MERGEDAY
  • FIREFOX_102_0_1_RELEASE
  • FIREFOX_102_10_0esr_BUILD1
  • FIREFOX_102_11_0esr_BUILD2
  • FIREFOX_102_12_0esr_BUILD1
  • FIREFOX_102_13_0esr_BUILD1
  • FIREFOX_102_14_0esr_BUILD1
  • FIREFOX_102_15_0esr_BUILD1
  • FIREFOX_102_15_1esr_BUILD1
  • FIREFOX_102_2_0esr_BUILD2
  • FIREFOX_102_3_0esr_RELEASE
  • FIREFOX_102_4_0esr_BUILD1
  • FIREFOX_102_5_0esr_RELEASE
  • FIREFOX_102_6_0esr_BUILD1
  • FIREFOX_102_7_0esr_BUILD1
  • FIREFOX_102_8_0esr_BUILD1
  • FIREFOX_102_9_0esr_BUILD1
  • FIREFOX_115_0_2esr_RELEASE
  • FIREFOX_115_0b3_RELEASE
  • FIREFOX_115_0b4_RELEASE
  • FIREFOX_115_0b5_RELEASE
  • FIREFOX_115_10_0esr_BUILD1
  • FIREFOX_115_11_0esr_BUILD1
  • FIREFOX_115_12_0esr_BUILD1
  • FIREFOX_115_13_0esr_BUILD3
  • FIREFOX_115_14_0esr_BUILD1
  • FIREFOX_115_15_0esr_BUILD1
  • FIREFOX_115_16_0esr_BUILD1
  • FIREFOX_115_17_0esr_BUILD1
  • FIREFOX_115_18_0esr_BUILD1
  • FIREFOX_115_19_0esr_BUILD1
  • FIREFOX_115_1_0esr_BUILD1
  • FIREFOX_115_20_0esr_BUILD1
  • FIREFOX_115_21_0esr_BUILD1
  • FIREFOX_115_22_0esr_BUILD1
  • FIREFOX_115_22_0esr_BUILD2
  • FIREFOX_115_23_0esr_BUILD1
  • FIREFOX_115_23_1esr_BUILD1
  • FIREFOX_115_24_0esr_BUILD1
  • FIREFOX_115_25_0esr_BUILD1
  • FIREFOX_115_26_0esr_BUILD1
  • FIREFOX_115_27_0esr_BUILD1
  • FIREFOX_115_28_0esr_BUILD1
  • FIREFOX_115_2_0esr_BUILD1
  • FIREFOX_115_2_1esr_BUILD1
  • FIREFOX_115_30_0esr_BUILD1
  • FIREFOX_115_31_0esr_BUILD1
  • FIREFOX_115_3_0esr_BUILD1
  • FIREFOX_115_3_1esr_RELEASE
  • FIREFOX_115_4_0esr_BUILD1
  • FIREFOX_115_5_0esr_BUILD1
  • FIREFOX_115_6_0esr_BUILD1
  • FIREFOX_115_7_0esr_BUILD1
  • FIREFOX_115_8_0esr_BUILD1
  • FIREFOX_115_9_0esr_BUILD1
  • FIREFOX_115_9_1esr_BUILD1
  • FIREFOX_116_0_3_RELEASE
  • FIREFOX_117_0_RELEASE
  • FIREFOX_118_0_RELEASE
  • FIREFOX_119_0_RELEASE
  • FIREFOX_120_0_RELEASE
  • FIREFOX_121_0_RELEASE
  • FIREFOX_122_0_RELEASE
  • FIREFOX_123_0_RELEASE
  • FIREFOX_124_0_RELEASE
  • FIREFOX_125_0_BUILD1
  • FIREFOX_126_0_BUILD1
  • FIREFOX_127_0b1_RELEASE
  • FIREFOX_128_0b1_BUILD1
  • FIREFOX_128_0esr_RELEASE
  • FIREFOX_128_10_0esr_BUILD1
  • FIREFOX_128_11_0esr_BUILD1
  • FIREFOX_128_12_0esr_BUILD1
  • FIREFOX_128_13_0esr_BUILD1
  • FIREFOX_128_14_0esr_BUILD1
  • FIREFOX_128_1_0esr_BUILD1
  • FIREFOX_128_2_0esr_BUILD1
  • FIREFOX_128_3_0esr_BUILD1
  • FIREFOX_128_4_0esr_BUILD1
  • FIREFOX_128_5_0esr_BUILD1
  • FIREFOX_128_6_0esr_BUILD1
  • FIREFOX_128_7_0esr_BUILD1
  • FIREFOX_128_8_0esr_BUILD1
  • FIREFOX_128_9_0esr_BUILD1
  • FIREFOX_128_9_0esr_BUILD2
  • FIREFOX_140_0esr_RELEASE
  • FIREFOX_140_1_0esr_BUILD1
  • FIREFOX_140_2_0esr_BUILD1
  • FIREFOX_140_3_0esr_BUILD1
  • FIREFOX_140_4_0esr_BUILD1
  • FIREFOX_140_5_0esr_BUILD1
  • FIREFOX_140_6_0esr_BUILD1
  • FIREFOX_91_10_0esr_BUILD1
  • FIREFOX_91_11_0esr_BUILD1
  • FIREFOX_91_12_0esr_BUILD1
  • FIREFOX_91_13_0esr_BUILD1
  • FIREFOX_91_7_0esr_BUILD2
  • FIREFOX_91_8_0esr_BUILD1
200 results
Show changes

Commits on Source 4

7 files
+ 272
284
Compare changes
  • Side-by-side
  • Inline

Files

+2 −4
Original line number Diff line number Diff line
// Copyright (c) 2022, The Tor Project, Inc.

import { TorStrings } from "resource://gre/modules/TorStrings.sys.mjs";
import {
  OnionAliasStore,
  OnionAliasStoreTopics,
@@ -8,8 +7,8 @@ import {

const kShowWarningPref = "torbrowser.rulesets.show_warning";

// This class allows about:rulesets to get TorStrings and to load/save the
// preference for skipping the warning
// This class allows about:rulesets to load/save the preference for skipping the
// warning
export class RulesetsParent extends JSWindowActorParent {
  constructor(...args) {
    super(...args);
@@ -53,7 +52,6 @@ export class RulesetsParent extends JSWindowActorParent {
        return OnionAliasStore.getChannels();
      case "rulesets:get-init-args":
        return {
          TorStrings,
          showWarning: Services.prefs.getBoolPref(kShowWarningPref, true),
        };
      case "rulesets:set-channel":
+88 −22
Original line number Diff line number Diff line
@@ -11,13 +11,19 @@
      rel="stylesheet"
      href="chrome://browser/content/rulesets/aboutRulesets.css"
    />

    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="browser/tor-browser.ftl" />
  </head>
  <body>
    <!-- Warning -->
    <div id="warning-wrapper">
      <div id="warning">
        <h1 id="warning-title"></h1>
        <p id="warning-description"></p>
        <h1 id="warning-title" data-l10n-id="rulesets-warning-heading"></h1>
        <p
          id="warning-description"
          data-l10n-id="rulesets-warning-description"
        ></p>
        <p>
          <label>
            <input
@@ -25,11 +31,18 @@
              type="checkbox"
              checked="checked"
            />
            <span id="warning-enable-label"></span>
            <span
              id="warning-enable-label"
              data-l10n-id="rulesets-warning-checkbox"
            ></span>
          </label>
        </p>
        <div id="warning-buttonbar">
          <button id="warning-button" autofocus="autofocus"></button>
          <button
            id="warning-button"
            autofocus="autofocus"
            data-l10n-id="rulesets-warning-continue-button"
          ></button>
        </div>
      </div>
    </div>
@@ -37,11 +50,20 @@
    <div id="main-content">
      <!-- Ruleset list -->
      <aside>
        <div id="ruleset-heading"></div>
        <div
          id="ruleset-heading"
          data-l10n-id="rulesets-side-panel-heading"
        ></div>
        <div id="ruleset-list-container">
          <div id="ruleset-list-empty">
            <p id="ruleset-list-empty-title"></p>
            <p id="ruleset-list-empty-description"></p>
            <p
              id="ruleset-list-empty-title"
              data-l10n-id="rulesets-side-panel-no-rules"
            ></p>
            <p
              id="ruleset-list-empty-description"
              data-l10n-id="rulesets-side-panel-no-rules-description"
            ></p>
          </div>
          <ul id="ruleset-list">
            <li id="ruleset-template">
@@ -59,24 +81,40 @@
      <section id="ruleset-details">
        <div class="title">
          <h1 id="ruleset-title"></h1>
          <button id="ruleset-edit" class="ghost-button"></button>
          <button
            id="ruleset-edit"
            class="ghost-button"
            data-l10n-id="rulesets-details-edit-button"
          ></button>
        </div>
        <dl>
          <dt id="ruleset-jwk-label"></dt>
          <dt id="ruleset-jwk-label" data-l10n-id="rulesets-details-jwk"></dt>
          <dd id="ruleset-jwk-value"></dd>
          <dt id="ruleset-path-prefix-label"></dt>
          <dt
            id="ruleset-path-prefix-label"
            data-l10n-id="rulesets-details-path"
          ></dt>
          <dd>
            <a id="ruleset-path-prefix-value" target="_blank"></a>
          </dd>
          <dt id="ruleset-scope-label"></dt>
          <dt
            id="ruleset-scope-label"
            data-l10n-id="rulesets-details-scope"
          ></dt>
          <dd id="ruleset-scope-value"></dd>
        </dl>
        <label id="ruleset-enable">
          <input type="checkbox" id="ruleset-enable-checkbox" />
          <span id="ruleset-enable-label"></span>
          <span
            id="ruleset-enable-label"
            data-l10n-id="rulesets-details-enable-checkbox"
          ></span>
        </label>
        <div id="ruleset-buttonbar">
          <button id="ruleset-update-button"></button>
          <button
            id="ruleset-update-button"
            data-l10n-id="rulesets-details-update-button"
          ></button>
        </div>
        <hr />
        <p id="ruleset-updated"></p>
@@ -89,24 +127,52 @@
        </div>
        <form id="edit-ruleset-form">
          <label>
            <div id="edit-jwk-label"></div>
            <textarea id="edit-jwk-textarea" rows="10"></textarea>
            <div id="edit-jwk-label" data-l10n-id="rulesets-details-jwk"></div>
            <textarea
              id="edit-jwk-textarea"
              rows="10"
              data-l10n-id="rulesets-details-jwk-input"
            ></textarea>
          </label>
          <label>
            <div id="edit-path-prefix-label"></div>
            <input id="edit-path-prefix-input" type="text" />
            <div
              id="edit-path-prefix-label"
              data-l10n-id="rulesets-details-path"
            ></div>
            <input
              id="edit-path-prefix-input"
              type="text"
              data-l10n-id="rulesets-details-path-input"
            />
          </label>
          <label>
            <div id="edit-scope-label"></div>
            <input id="edit-scope-input" type="text" />
            <div
              id="edit-scope-label"
              data-l10n-id="rulesets-details-scope"
            ></div>
            <input
              id="edit-scope-input"
              type="text"
              data-l10n-id="rulesets-details-scope-input"
            />
          </label>
          <label id="edit-enable">
            <input type="checkbox" id="edit-enable-checkbox" />
            <span id="edit-enable-label"></span>
            <span
              id="edit-enable-label"
              data-l10n-id="rulesets-details-enable-checkbox"
            ></span>
          </label>
          <div id="edit-buttonbar">
            <button id="edit-save" class="primary"></button>
            <button id="edit-cancel"></button>
            <button
              id="edit-save"
              class="primary"
              data-l10n-id="rulesets-details-save-button"
            ></button>
            <button
              id="edit-cancel"
              data-l10n-id="rulesets-details-cancel-button"
            ></button>
          </div>
        </form>
      </section>
+62 −168
Original line number Diff line number Diff line
@@ -2,8 +2,6 @@

/* globals RPMAddMessageListener, RPMSendQuery, RPMSendAsyncMessage */

let TorStrings;

const Orders = Object.freeze({
  Name: "name",
  NameDesc: "name-desc",
@@ -19,55 +17,35 @@ const States = Object.freeze({

function setUpdateDate(ruleset, element) {
  if (!ruleset.enabled) {
    element.textContent = TorStrings.rulesets.disabled;
    document.l10n.setAttributes(element, "rulesets-update-rule-disabled");
    return;
  }
  if (!ruleset.currentTimestamp) {
    element.textContent = TorStrings.rulesets.neverUpdated;
    document.l10n.setAttributes(element, "rulesets-update-never");
    return;
  }

  const formatter = new Intl.DateTimeFormat(navigator.languages, {
    year: "numeric",
    month: "long",
    day: "numeric",
  document.l10n.setAttributes(element, "rulesets-update-last", {
    date: ruleset.currentTimestamp * 1000,
  });
  element.textContent = TorStrings.rulesets.lastUpdated.replace(
    "%S",
    formatter.format(new Date(ruleset.currentTimestamp * 1000))
  );
}

class WarningState {
  selectors = Object.freeze({
    wrapper: "#warning-wrapper",
    title: "#warning-title",
    description: "#warning-description",
    enableCheckbox: "#warning-enable-checkbox",
    enableLabel: "#warning-enable-label",
    button: "#warning-button",
  });

  elements = Object.freeze({
    wrapper: document.querySelector(this.selectors.wrapper),
    title: document.querySelector(this.selectors.title),
    description: document.querySelector(this.selectors.description),
    enableCheckbox: document.querySelector(this.selectors.enableCheckbox),
    enableLabel: document.querySelector(this.selectors.enableLabel),
    button: document.querySelector(this.selectors.button),
  });
  elements = {
    enableCheckbox: document.getElementById("warning-enable-checkbox"),
    button: document.getElementById("warning-button"),
  };

  constructor() {
    const elements = this.elements;
    elements.title.textContent = TorStrings.rulesets.warningTitle;
    elements.description.textContent = TorStrings.rulesets.warningDescription;
    elements.enableLabel.textContent = TorStrings.rulesets.warningEnable;
    elements.button.textContent = TorStrings.rulesets.warningButton;
    elements.enableCheckbox.addEventListener(
    this.elements.enableCheckbox.addEventListener(
      "change",
      this.onEnableChange.bind(this)
    );
    elements.button.addEventListener("click", this.onButtonClick.bind(this));

    this.elements.button.addEventListener(
      "click",
      this.onButtonClick.bind(this)
    );
  }

  show() {
@@ -89,50 +67,28 @@ class WarningState {
}

class DetailsState {
  selectors = Object.freeze({
    title: "#ruleset-title",
    edit: "#ruleset-edit",
    jwkLabel: "#ruleset-jwk-label",
    jwkValue: "#ruleset-jwk-value",
    pathPrefixLabel: "#ruleset-path-prefix-label",
    pathPrefixValue: "#ruleset-path-prefix-value",
    scopeLabel: "#ruleset-scope-label",
    scopeValue: "#ruleset-scope-value",
    enableCheckbox: "#ruleset-enable-checkbox",
    enableLabel: "#ruleset-enable-label",
    updateButton: "#ruleset-update-button",
    updated: "#ruleset-updated",
  });

  elements = Object.freeze({
    title: document.querySelector(this.selectors.title),
    edit: document.querySelector(this.selectors.edit),
    jwkLabel: document.querySelector(this.selectors.jwkLabel),
    jwkValue: document.querySelector(this.selectors.jwkValue),
    pathPrefixLabel: document.querySelector(this.selectors.pathPrefixLabel),
    pathPrefixValue: document.querySelector(this.selectors.pathPrefixValue),
    scopeLabel: document.querySelector(this.selectors.scopeLabel),
    scopeValue: document.querySelector(this.selectors.scopeValue),
    enableCheckbox: document.querySelector(this.selectors.enableCheckbox),
    enableLabel: document.querySelector(this.selectors.enableLabel),
    updateButton: document.querySelector(this.selectors.updateButton),
    updated: document.querySelector(this.selectors.updated),
  });
  elements = {
    title: document.getElementById("ruleset-title"),
    jwkValue: document.getElementById("ruleset-jwk-value"),
    pathPrefixValue: document.getElementById("ruleset-path-prefix-value"),
    scopeValue: document.getElementById("ruleset-scope-value"),
    enableCheckbox: document.getElementById("ruleset-enable-checkbox"),
    updateButton: document.getElementById("ruleset-update-button"),
    updated: document.getElementById("ruleset-updated"),
  };

  constructor() {
    const elements = this.elements;
    elements.edit.textContent = TorStrings.rulesets.edit;
    elements.edit.addEventListener("click", this.onEdit.bind(this));
    elements.jwkLabel.textContent = TorStrings.rulesets.jwk;
    elements.pathPrefixLabel.textContent = TorStrings.rulesets.pathPrefix;
    elements.scopeLabel.textContent = TorStrings.rulesets.scope;
    elements.enableCheckbox.addEventListener(
    document
      .getElementById("ruleset-edit")
      .addEventListener("click", this.onEdit.bind(this));
    this.elements.enableCheckbox.addEventListener(
      "change",
      this.onEnable.bind(this)
    );
    elements.enableLabel.textContent = TorStrings.rulesets.enable;
    elements.updateButton.textContent = TorStrings.rulesets.checkUpdates;
    elements.updateButton.addEventListener("click", this.onUpdate.bind(this));
    this.elements.updateButton.addEventListener(
      "click",
      this.onUpdate.bind(this)
    );
  }

  show(ruleset) {
@@ -179,61 +135,22 @@ class DetailsState {
}

class EditState {
  selectors = Object.freeze({
    form: "#edit-ruleset-form",
    title: "#edit-title",
    nameGroup: "#edit-name-group",
    nameLabel: "#edit-name-label",
    nameInput: "#edit-name-input",
    jwkLabel: "#edit-jwk-label",
    jwkTextarea: "#edit-jwk-textarea",
    pathPrefixLabel: "#edit-path-prefix-label",
    pathPrefixInput: "#edit-path-prefix-input",
    scopeLabel: "#edit-scope-label",
    scopeInput: "#edit-scope-input",
    enableCheckbox: "#edit-enable-checkbox",
    enableLabel: "#edit-enable-label",
    save: "#edit-save",
    cancel: "#edit-cancel",
  });

  elements = Object.freeze({
    form: document.querySelector(this.selectors.form),
    title: document.querySelector(this.selectors.title),
    jwkLabel: document.querySelector(this.selectors.jwkLabel),
    jwkTextarea: document.querySelector(this.selectors.jwkTextarea),
    pathPrefixLabel: document.querySelector(this.selectors.pathPrefixLabel),
    pathPrefixInput: document.querySelector(this.selectors.pathPrefixInput),
    scopeLabel: document.querySelector(this.selectors.scopeLabel),
    scopeInput: document.querySelector(this.selectors.scopeInput),
    enableCheckbox: document.querySelector(this.selectors.enableCheckbox),
    enableLabel: document.querySelector(this.selectors.enableLabel),
    save: document.querySelector(this.selectors.save),
    cancel: document.querySelector(this.selectors.cancel),
  });
  elements = {
    form: document.getElementById("edit-ruleset-form"),
    title: document.getElementById("edit-title"),
    jwkTextarea: document.getElementById("edit-jwk-textarea"),
    pathPrefixInput: document.getElementById("edit-path-prefix-input"),
    scopeInput: document.getElementById("edit-scope-input"),
    enableCheckbox: document.getElementById("edit-enable-checkbox"),
  };

  constructor() {
    const elements = this.elements;
    elements.jwkLabel.textContent = TorStrings.rulesets.jwk;
    elements.jwkTextarea.setAttribute(
      "placeholder",
      TorStrings.rulesets.jwkPlaceholder
    );
    elements.pathPrefixLabel.textContent = TorStrings.rulesets.pathPrefix;
    elements.pathPrefixInput.setAttribute(
      "placeholder",
      TorStrings.rulesets.pathPrefixPlaceholder
    );
    elements.scopeLabel.textContent = TorStrings.rulesets.scope;
    elements.scopeInput.setAttribute(
      "placeholder",
      TorStrings.rulesets.scopePlaceholder
    );
    elements.enableLabel.textContent = TorStrings.rulesets.enable;
    elements.save.textContent = TorStrings.rulesets.save;
    elements.save.addEventListener("click", this.onSave.bind(this));
    elements.cancel.textContent = TorStrings.rulesets.cancel;
    elements.cancel.addEventListener("click", this.onCancel.bind(this));
    document
      .getElementById("edit-save")
      .addEventListener("click", this.onSave.bind(this));
    document
      .getElementById("edit-cancel")
      .addEventListener("click", this.onCancel.bind(this));
  }

  show(ruleset) {
@@ -276,7 +193,9 @@ class EditState {
      elements.jwkTextarea.setCustomValidity("");
    } catch (err) {
      console.error("Invalid JSON or invalid JWK", err);
      elements.jwkTextarea.setCustomValidity(TorStrings.rulesets.jwkInvalid);
      elements.jwkTextarea.setCustomValidity(
        await document.l10n.formatValue("rulesets-details-jwk-input-invalid")
      );
      valid = false;
    }

@@ -285,7 +204,7 @@ class EditState {
      const url = new URL(pathPrefix);
      if (url.protocol !== "http:" && url.protocol !== "https:") {
        elements.pathPrefixInput.setCustomValidity(
          TorStrings.rulesets.pathPrefixInvalid
          await document.l10n.formatValue("rulesets-details-path-input-invalid")
        );
        valid = false;
      } else {
@@ -294,7 +213,7 @@ class EditState {
    } catch (err) {
      console.error("The path prefix is not a valid URL", err);
      elements.pathPrefixInput.setCustomValidity(
        TorStrings.rulesets.pathPrefixInvalid
        await document.l10n.formatValue("rulesets-details-path-input-invalid")
      );
      valid = false;
    }
@@ -304,7 +223,9 @@ class EditState {
      scope = new RegExp(elements.scopeInput.value.trim());
      elements.scopeInput.setCustomValidity("");
    } catch (err) {
      elements.scopeInput.setCustomValidity(TorStrings.rulesets.scopeInvalid);
      elements.scopeInput.setCustomValidity(
        await document.l10n.formatValue("rulesets-details-scope-input-invalid")
      );
      valid = false;
    }

@@ -342,39 +263,17 @@ class NoRulesetsState {
}

class RulesetList {
  selectors = Object.freeze({
    heading: "#ruleset-heading",
    list: "#ruleset-list",
    emptyContainer: "#ruleset-list-empty",
    emptyTitle: "#ruleset-list-empty-title",
    emptyDescription: "#ruleset-list-empty-description",
    itemTemplate: "#ruleset-template",
    itemName: ".name",
    itemDescr: ".description",
  });

  elements = Object.freeze({
    heading: document.querySelector(this.selectors.heading),
    list: document.querySelector(this.selectors.list),
    emptyContainer: document.querySelector(this.selectors.emptyContainer),
    emptyTitle: document.querySelector(this.selectors.emptyTitle),
    emptyDescription: document.querySelector(this.selectors.emptyDescription),
    itemTemplate: document.querySelector(this.selectors.itemTemplate),
  });
  elements = {
    list: document.getElementById("ruleset-list"),
    emptyContainer: document.getElementById("ruleset-list-empty"),
    itemTemplate: document.getElementById("ruleset-template"),
  };

  nameAttribute = "data-name";

  rulesets = [];

  constructor() {
    const elements = this.elements;

    // Header
    elements.heading.textContent = TorStrings.rulesets.rulesets;
    // Empty
    elements.emptyTitle.textContent = TorStrings.rulesets.noRulesets;
    elements.emptyDescription.textContent = TorStrings.rulesets.noRulesetsDescr;

    RPMAddMessageListener(
      "rulesets:channels-change",
      this.onRulesetsChanged.bind(this)
@@ -438,14 +337,10 @@ class RulesetList {
    const item = this.elements.itemTemplate.cloneNode(true);
    item.removeAttribute("id");
    item.classList.add("item");
    item.querySelector(this.selectors.itemName).textContent = ruleset.name;
    const descr = item.querySelector(this.selectors.itemDescr);
    if (ruleset.enabled) {
    item.querySelector(".name").textContent = ruleset.name;
    const descr = item.querySelector(".description");
    setUpdateDate(ruleset, descr);
    } else {
      descr.textContent = TorStrings.rulesets.disabled;
      item.classList.add("disabled");
    }
    item.classList.toggle("disabled", !ruleset.enabled);
    item.setAttribute(this.nameAttribute, ruleset.name);
    item.addEventListener("click", () => {
      this.onRulesetClick(ruleset);
@@ -478,7 +373,6 @@ class AboutRulesets {

  async init() {
    const args = await RPMSendQuery("rulesets:get-init-args");
    TorStrings = args.TorStrings;
    const showWarning = args.showWarning;

    this.list = new RulesetList();
+50 −0
Original line number Diff line number Diff line
@@ -557,3 +557,53 @@ downloads-tor-warning-title = Be careful opening downloads
downloads-tor-warning-description = Some files may connect to the internet when opened without using Tor. To be safe, open the files while offline or use a portable operating system like <a data-l10n-name="tails-link">Tails</a>.
# Button to dismiss the warning forever.
downloads-tor-warning-dismiss-button = Got it

## Initial warning page in about:rulesets. In Tor Browser, each ruleset is a set of rules for converting a ".tor.onion" address to a normal ".onion" address (used by SecureDrop). The feature is taken from the discontinued "HTTPS Everywhere".

rulesets-warning-heading = Proceed with Caution
rulesets-warning-description = Adding or modifying rulesets can cause attackers to hijack your browser. Proceed only if you know what you are doing.
rulesets-warning-checkbox = Warn me when I attempt to access these preferences
rulesets-warning-continue-button = Accept the Risk and Continue

## Side panel in about:rulesets. In Tor Browser, each ruleset is a set of rules for converting a ".tor.onion" address to a normal ".onion" address (used by SecureDrop). The feature is taken from the discontinued "HTTPS Everywhere".

rulesets-side-panel-heading = Rulesets
rulesets-side-panel-no-rules = No rulesets found
# -brand-short-name refers to 'Tor Browser', localized.
rulesets-side-panel-no-rules-description = When you save a ruleset in { -brand-short-name }, it will show up here.

## Ruleset update date in about:rulesets.

# $date (Date) - The update date. The DATETIME function will format the $date according to the locale, using a "long" style. E.g. "January 1, 2000" for English (US), "١ يناير ٢٠٠٠" for Arabic, "2000년 1월 1일" in Korean, and "1 января 2000 г." in Russian.
rulesets-update-last = Last updated { DATETIME($date, dateStyle: "long") }
rulesets-update-never = Never updated, or last update failed
# Shown when the ruleset is disabled.
rulesets-update-rule-disabled = Disabled

## Ruleset details in about:rulesets. In Tor Browser, each ruleset is a set of rules for converting a ".tor.onion" address to a normal ".onion" address (used by SecureDrop). The feature is taken from the discontinued "HTTPS Everywhere".

rulesets-details-edit-button = Edit
rulesets-details-enable-checkbox = Enable this ruleset
rulesets-details-update-button = Check for Updates
rulesets-details-save-button = Save
rulesets-details-cancel-button = Cancel
# "JWK" refers to "JSON Web Key" and likely should not be translated.
rulesets-details-jwk = JWK
# "JWK" refers to "JSON Web Key" and likely should not be translated.
rulesets-details-jwk-input =
    .placeholder = The key used to sign this ruleset in the JWK (JSON Web Key) format
# "JWK" refers to "JSON Web Key" and likely should not be translated.
rulesets-details-jwk-input-invalid = The JWK could not be parsed, or it is not a valid key
# "Path" refers to the URL domain this rule applies to.
rulesets-details-path = Path Prefix
rulesets-details-path-input =
    .placeholder = URL prefix that contains the files needed by the ruleset
# "HTTP(S)" refers to "HTTP or HTTPS".
rulesets-details-path-input-invalid = The path prefix is not a valid HTTP(S) URL
# "Scope" refers to the breadth of URLs this rule applies to (as a regular expression).
rulesets-details-scope = Scope
# "Regular expression" refers to the computing term for a special pattern used for matching: https://en.wikipedia.org/wiki/Regular_expression.
rulesets-details-scope-input =
    .placeholder = Regular expression for the scope of the rules
# "Regular expression" refers to the computing term for a special pattern used for matching: https://en.wikipedia.org/wiki/Regular_expression.
rulesets-details-scope-input-invalid = The scope could not be parsed as a regular expression
+0 −55
Original line number Diff line number Diff line
@@ -429,54 +429,6 @@ const Loader = {
      learnMoreURLNotification: `https://tb-manual.torproject.org/${getLocale()}/onion-services/`,
    };
  } /* OnionLocation */,

  /*
    Rulesets
  */
  rulesets() {
    const strings = {
      // Initial warning
      warningTitle: "Proceed with Caution",
      warningDescription:
        "Adding or modifying rulesets can cause attackers to hijack your browser. Proceed only if you know what you are doing.",
      warningEnable: "Warn me when I attempt to access these preferences",
      warningButton: "Accept the Risk and Continue",
      // Ruleset list
      rulesets: "Rulesets",
      noRulesets: "No rulesets found",
      noRulesetsDescr:
        "When you save a ruleset in Tor Browser, it will show up here.",
      lastUpdated: "Last updated %S",
      neverUpdated: "Never updated, or last update failed",
      enabled: "Enabled",
      disabled: "Disabled",
      // Ruleset details
      edit: "Edit",
      name: "Name",
      jwk: "JWK",
      pathPrefix: "Path Prefix",
      scope: "Scope",
      enable: "Enable this ruleset",
      checkUpdates: "Check for Updates",
      // Add ruleset
      jwkPlaceholder:
        "The key used to sign this ruleset in the JWK (JSON Web Key) format",
      jwkInvalid: "The JWK could not be parsed, or it is not a valid key",
      pathPrefixPlaceholder:
        "URL prefix that contains the files needed by the ruleset",
      pathPrefixInvalid: "The path prefix is not a valid HTTP(S) URL",
      scopePlaceholder: "Regular expression for the scope of the rules",
      scopeInvalid: "The scope could not be parsed as a regular expression",
      save: "Save",
      cancel: "Cancel",
    };

    const tsb = new TorPropertyStringBundle(
      ["chrome://torbutton/locale/rulesets.properties"],
      "rulesets."
    );
    return tsb.getStrings(strings);
  } /* Rulesets */,
};

export const TorStrings = {
@@ -507,11 +459,4 @@ export const TorStrings = {
    }
    return this._onionLocation;
  },

  get rulesets() {
    if (!this._rulesets) {
      this._rulesets = Loader.rulesets();
    }
    return this._rulesets;
  },
};
+0 −35
Original line number Diff line number Diff line
# Copyright (c) 2022, The Tor Project, Inc.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# about:rulesets strings.
rulesets.warningTitle=Proceed with Caution
rulesets.warningDescription=Adding or modifying rulesets can cause attackers to hijack your browser. Proceed only if you know what you are doing.
rulesets.warningEnable=Warn me when I attempt to access these preferences
rulesets.warningButton=Accept the Risk and Continue
# Ruleset list
rulesets.rulesets=Rulesets
rulesets.noRulesets=No rulesets found
rulesets.noRulesetsDescr=When you save a ruleset in Tor Browser, it will show up here.
# LOCALIZATION NOTE: %S will be replaced by the update date (automatically formatted by Firefox's l10n component)
rulesets.lastUpdated=Last updated %S
rulesets.neverUpdated=Never updated, or last update failed
rulesets.enabled=Enabled
rulesets.disabled=Disabled
# Ruleset details/edit ruleset
rulesets.edit=Edit
rulesets.name=Name
rulesets.jwk=JWK
rulesets.pathPrefix=Path Prefix
rulesets.scope=Scope
rulesets.enable=Enable this ruleset
rulesets.checkUpdates=Check for Updates
rulesets.jwkPlaceholder=The key used to sign this ruleset in the JWK (JSON Web Key) format
rulesets.jwkInvalid=The JWK could not be parsed, or it is not a valid key
rulesets.pathPrefixPlaceholder=URL prefix that contains the files needed by the ruleset
rulesets.pathPrefixInvalid=The path prefix is not a valid HTTP(S) URL
rulesets.scopePlaceholder=Regular expression for the scope of the rules
rulesets.scopeInvalid=The scope could not be parsed as a regular expression
rulesets.save=Save
rulesets.cancel=Cancel
+70 −0
Original line number Diff line number Diff line
import fluent.syntax.ast as FTL
from fluent.migrate.helpers import transforms_from
from fluent.migrate.transforms import REPLACE


def migrate(ctx):
    legacy_path = "rulesets.properties"

    ctx.add_transforms(
        "tor-browser.ftl",
        "tor-browser.ftl",
        transforms_from(
            """
rulesets-warning-heading = { COPY(path, "rulesets.warningTitle") }
rulesets-warning-description = { COPY(path, "rulesets.warningDescription") }
rulesets-warning-checkbox = { COPY(path, "rulesets.warningEnable") }
rulesets-warning-continue-button = { COPY(path, "rulesets.warningButton") }

rulesets-side-panel-heading = { COPY(path, "rulesets.rulesets") }
rulesets-side-panel-no-rules = { COPY(path, "rulesets.noRulesets") }

rulesets-update-never = { COPY(path, "rulesets.neverUpdated") }
rulesets-update-rule-disabled = { COPY(path, "rulesets.disabled") }

rulesets-details-edit-button = { COPY(path, "rulesets.edit") }
rulesets-details-enable-checkbox = { COPY(path, "rulesets.enable") }
rulesets-details-update-button = { COPY(path, "rulesets.checkUpdates") }
rulesets-details-save-button = { COPY(path, "rulesets.save") }
rulesets-details-cancel-button = { COPY(path, "rulesets.cancel") }
rulesets-details-jwk-input =
    .placeholder = { COPY(path, "rulesets.jwkPlaceholder") }
rulesets-details-jwk-input-invalid = { COPY(path, "rulesets.jwkInvalid") }
rulesets-details-path = { COPY(path, "rulesets.pathPrefix") }
rulesets-details-path-input =
    .placeholder = { COPY(path, "rulesets.pathPrefixPlaceholder") }
rulesets-details-path-input-invalid = { COPY(path, "rulesets.pathPrefixInvalid") }
rulesets-details-scope = { COPY(path, "rulesets.scope") }
rulesets-details-scope-input =
    .placeholder = { COPY(path, "rulesets.scopePlaceholder") }
rulesets-details-scope-input-invalid = { COPY(path, "rulesets.scopeInvalid") }
""",
            path=legacy_path,
        )
        + [
            # Replace "%1$S" with "{ DATETIME($date, dateStyle: "long") }"
            FTL.Message(
                FTL.Identifier("rulesets-update-last"),
                value=REPLACE(
                    legacy_path,
                    "rulesets.lastUpdated",
                    {
                        "%1$S": FTL.FunctionReference(
                            FTL.Identifier("DATETIME"),
                            arguments=FTL.CallArguments(
                                positional=[
                                    FTL.VariableReference(FTL.Identifier("date"))
                                ],
                                named=[
                                    FTL.NamedArgument(
                                        FTL.Identifier("dateStyle"),
                                        value=FTL.StringLiteral("long"),
                                    )
                                ],
                            ),
                        )
                    },
                ),
            ),
        ],
    )