summaryrefslogtreecommitdiffstats
path: root/python/l10n
diff options
context:
space:
mode:
Diffstat (limited to 'python/l10n')
-rw-r--r--python/l10n/fluent_migrations/__init__.py0
-rw-r--r--python/l10n/fluent_migrations/bug_1552333_aboutCertError.py40
-rw-r--r--python/l10n/fluent_migrations/bug_1635548_browser_context.py82
-rw-r--r--python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py65
-rw-r--r--python/l10n/fluent_migrations/bug_1793572_webrtc.py771
-rw-r--r--python/l10n/fluent_migrations/bug_1812135_newtab_moz_toggle_labels.py30
-rw-r--r--python/l10n/fluent_migrations/bug_1814969_contextualIdentity.py101
-rw-r--r--python/l10n/fluent_migrations/bug_1844783_mozMessageBarShopping.py45
-rw-r--r--python/l10n/fluent_migrations/bug_1844850_mozMessageBar_unifiedExtensionsPanel.py36
-rw-r--r--python/l10n/fluent_migrations/bug_1845150_search_engine_notification.py32
-rw-r--r--python/l10n/fluent_migrations/bug_1845727_mozSupportLink_to_toolkit.py21
-rw-r--r--python/l10n/fluent_migrations/bug_1851877_mozMessageBar_aboutAddons_uninstallBar.py41
-rw-r--r--python/l10n/fluent_migrations/bug_1854425_default_agent.py58
-rw-r--r--python/l10n/fluent_migrations/bug_1856014_protectionsPanel_fixButtonLabels.py36
-rw-r--r--python/l10n/fluent_migrations/bug_1858715_pdfjs.py1078
-rw-r--r--python/l10n/fluent_migrations/bug_1860606_remove_migration_ftl.py27
-rw-r--r--python/l10n/fluent_migrations/bug_1862982_protectionsPanel_fixCancelButtonLabel.py21
-rw-r--r--python/l10n/fluent_migrations/bug_1863022_protectionsPanel_infomessage.py24
-rw-r--r--python/l10n/fluent_migrations/bug_1866268_geckoViewConsole.py52
-rw-r--r--python/l10n/fluent_migrations/bug_1866295_new_device_migration_strings.py22
-rw-r--r--python/l10n/fluent_migrations/bug_1867346_new_device_migration_string_replacement.py22
-rw-r--r--python/l10n/fluent_migrations/bug_1867819_formautofill.py26
-rw-r--r--python/l10n/fluent_migrations/bug_1868154_RFPHelper.py22
-rw-r--r--python/l10n/fluent_migrations/bug_1869024_passwordmgr.py122
-rw-r--r--python/l10n/fluent_migrations/bug_1869389_urlbar_search_mode_indicator_close_button_addAccessibleName.py21
-rw-r--r--python/l10n/fluent_migrations/bug_747301_about_plugin_removal.py28
-rw-r--r--python/l10n/mozxchannel/__init__.py150
-rw-r--r--python/l10n/mozxchannel/projectconfig.py77
-rw-r--r--python/l10n/mozxchannel/source.py88
-rw-r--r--python/l10n/test_fluent_migrations/__init__.py0
-rw-r--r--python/l10n/test_fluent_migrations/fmt.py198
31 files changed, 3336 insertions, 0 deletions
diff --git a/python/l10n/fluent_migrations/__init__.py b/python/l10n/fluent_migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/l10n/fluent_migrations/__init__.py
diff --git a/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py
new file mode 100644
index 0000000000..5c8300e01f
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py
@@ -0,0 +1,40 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate.helpers import VARIABLE_REFERENCE
+from fluent.migrate import COPY, REPLACE
+
+
+def migrate(ctx):
+ """Bug 1552333 - Migrate strings from pipnss.properties to aboutCertError.ftl"""
+ ctx.add_transforms(
+ "browser/browser/aboutCertError.ftl",
+ "browser/browser/aboutCertError.ftl",
+ transforms_from(
+ """
+cert-error-symantec-distrust-admin = { COPY(from_path, "certErrorSymantecDistrustAdministrator") }
+""",
+ from_path="security/manager/chrome/pipnss/pipnss.properties",
+ ),
+ )
+ ctx.add_transforms(
+ "browser/browser/aboutCertError.ftl",
+ "browser/browser/aboutCertError.ftl",
+ [
+ FTL.Message(
+ id=FTL.Identifier("cert-error-symantec-distrust-description"),
+ value=REPLACE(
+ "security/manager/chrome/pipnss/pipnss.properties",
+ "certErrorSymantecDistrustDescription1",
+ {
+ "%1$S": VARIABLE_REFERENCE("hostname"),
+ },
+ normalize_printf=True,
+ ),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1635548_browser_context.py b/python/l10n/fluent_migrations/bug_1635548_browser_context.py
new file mode 100644
index 0000000000..33bd0efc95
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1635548_browser_context.py
@@ -0,0 +1,82 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from, VARIABLE_REFERENCE
+from fluent.migrate import REPLACE, COPY
+
+
+def migrate(ctx):
+ """Bug 1635548 - Migrate browser-context.inc to Fluent, part {index}"""
+ target = "toolkit/toolkit/global/textActions.ftl"
+ reference = "toolkit/toolkit/global/textActions.ftl"
+ ctx.add_transforms(
+ target,
+ reference,
+ transforms_from(
+ """
+text-action-spell-add-to-dictionary =
+ .label = { COPY(from_path, "spellAddToDictionary.label") }
+ .accesskey = { COPY(from_path, "spellAddToDictionary.accesskey") }
+
+text-action-spell-undo-add-to-dictionary =
+ .label = { COPY(from_path, "spellUndoAddToDictionary.label") }
+ .accesskey = { COPY(from_path, "spellUndoAddToDictionary.accesskey") }
+
+text-action-spell-check-toggle =
+ .label = { COPY(from_path, "spellCheckToggle.label") }
+ .accesskey = { COPY(from_path, "spellCheckToggle.accesskey") }
+
+text-action-spell-dictionaries =
+ .label = { COPY(from_path, "spellDictionaries.label") }
+ .accesskey = { COPY(from_path, "spellDictionaries.accesskey") }
+""",
+ from_path="toolkit/chrome/global/textcontext.dtd",
+ ),
+ )
+
+ target = "toolkit/toolkit/global/textActions.ftl"
+ reference = "toolkit/toolkit/global/textActions.ftl"
+ ctx.add_transforms(
+ target,
+ reference,
+ transforms_from(
+ """
+text-action-spell-add-dictionaries =
+ .label = { COPY(from_path, "spellAddDictionaries.label") }
+ .accesskey = { COPY(from_path, "spellAddDictionaries.accesskey") }
+""",
+ from_path="browser/chrome/browser/browser.dtd",
+ ),
+ )
+
+ target = "browser/browser/browserContext.ftl"
+ reference = "browser/browser/browserContext.ftl"
+ ctx.add_transforms(
+ target,
+ reference,
+ [
+ FTL.Message(
+ id=FTL.Identifier("main-context-menu-open-link-in-container-tab"),
+ attributes=[
+ FTL.Attribute(
+ FTL.Identifier("label"),
+ REPLACE(
+ "browser/chrome/browser/browser.properties",
+ "userContextOpenLink.label",
+ {"%1$S": VARIABLE_REFERENCE("containerName")},
+ ),
+ ),
+ FTL.Attribute(
+ FTL.Identifier("accesskey"),
+ COPY(
+ "browser/chrome/browser/browser.dtd",
+ "openLinkCmdInTab.accesskey",
+ ),
+ ),
+ ],
+ )
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py b/python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py
new file mode 100644
index 0000000000..99c6673f92
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py
@@ -0,0 +1,65 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate.transforms import COPY
+
+
+def migrate(ctx):
+ """Bug 1786186 - Migrate mobile about:config to Fluent, part {index}"""
+
+ target = "mobile/android/mobile/android/aboutConfig.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ transforms_from(
+ """
+
+config-toolbar-search =
+ .placeholder = { COPY(path1, "toolbar.searchPlaceholder") }
+config-new-pref-name =
+ .placeholder = { COPY(path1, "newPref.namePlaceholder") }
+config-new-pref-value-boolean = { COPY(path1, "newPref.valueBoolean") }
+config-new-pref-value-string = { COPY(path1, "newPref.valueString") }
+config-new-pref-value-integer = { COPY(path1, "newPref.valueInteger") }
+config-new-pref-string =
+ .placeholder = { COPY(path1, "newPref.stringPlaceholder") }
+config-new-pref-number =
+ .placeholder = { COPY(path1, "newPref.numberPlaceholder") }
+config-new-pref-cancel-button = { COPY(path1, "newPref.cancelButton") }
+config-context-menu-copy-pref-name =
+ .label = { COPY(path1, "contextMenu.copyPrefName") }
+config-context-menu-copy-pref-value =
+ .label = { COPY(path1, "contextMenu.copyPrefValue") }
+""",
+ path1="mobile/android/chrome/config.dtd",
+ ),
+ )
+
+ source = "mobile/android/chrome/config.properties"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("config-new-pref-create-button"),
+ value=COPY(source, "newPref.createButton"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("config-new-pref-change-button"),
+ value=COPY(source, "newPref.changeButton"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("config-pref-toggle-button"),
+ value=COPY(source, "pref.toggleButton"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("config-pref-reset-button"),
+ value=COPY(source, "pref.resetButton"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1793572_webrtc.py b/python/l10n/fluent_migrations/bug_1793572_webrtc.py
new file mode 100644
index 0000000000..eb07f939a8
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1793572_webrtc.py
@@ -0,0 +1,771 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import TERM_REFERENCE, VARIABLE_REFERENCE
+from fluent.migrate.transforms import (
+ COPY,
+ COPY_PATTERN,
+ PLURALS,
+ REPLACE,
+ REPLACE_IN_TEXT,
+)
+
+
+def migrate(ctx):
+ """Bug 1793572 - Convert WebRTC strings to Fluent, part {index}."""
+
+ source = "browser/chrome/browser/webrtcIndicator.properties"
+ browser = "browser/chrome/browser/browser.properties"
+ browser_ftl = "browser/browser/browser.ftl"
+ target = "browser/browser/webrtcIndicator.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-window"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY_PATTERN(target, "webrtc-indicator-title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-sharing-camera-and-microphone"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("tooltiptext"),
+ value=COPY(
+ source, "webrtcIndicator.sharingCameraAndMicrophone.tooltip"
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-sharing-camera"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("tooltiptext"),
+ value=COPY(source, "webrtcIndicator.sharingCamera.tooltip"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-sharing-microphone"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("tooltiptext"),
+ value=COPY(source, "webrtcIndicator.sharingMicrophone.tooltip"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-sharing-application"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("tooltiptext"),
+ value=COPY(
+ source, "webrtcIndicator.sharingApplication.tooltip"
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-sharing-screen"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("tooltiptext"),
+ value=COPY(source, "webrtcIndicator.sharingScreen.tooltip"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-sharing-window"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("tooltiptext"),
+ value=COPY(source, "webrtcIndicator.sharingWindow.tooltip"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-sharing-browser"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("tooltiptext"),
+ value=COPY(source, "webrtcIndicator.sharingBrowser.tooltip"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-control-sharing"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "webrtcIndicator.controlSharing.menuitem"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-control-sharing-on"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "webrtcIndicator.controlSharingOn.menuitem",
+ {"%1$S": VARIABLE_REFERENCE("streamTitle")},
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-sharing-camera-with"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "webrtcIndicator.sharingCameraWith.menuitem",
+ {"%1$S": VARIABLE_REFERENCE("streamTitle")},
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-sharing-microphone-with"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "webrtcIndicator.sharingMicrophoneWith.menuitem",
+ {"%1$S": VARIABLE_REFERENCE("streamTitle")},
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-sharing-application-with"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "webrtcIndicator.sharingApplicationWith.menuitem",
+ {"%1$S": VARIABLE_REFERENCE("streamTitle")},
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-sharing-screen-with"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "webrtcIndicator.sharingScreenWith.menuitem",
+ {"%1$S": VARIABLE_REFERENCE("streamTitle")},
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-sharing-window-with"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "webrtcIndicator.sharingWindowWith.menuitem",
+ {"%1$S": VARIABLE_REFERENCE("streamTitle")},
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-indicator-menuitem-sharing-browser-with"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "webrtcIndicator.sharingBrowserWith.menuitem",
+ {"%1$S": VARIABLE_REFERENCE("streamTitle")},
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-indicator-menuitem-sharing-camera-with-n-tabs"
+ ),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ source,
+ "webrtcIndicator.sharingCameraWithNTabs.menuitem",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-indicator-menuitem-sharing-microphone-with-n-tabs"
+ ),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ source,
+ "webrtcIndicator.sharingMicrophoneWithNTabs.menuitem",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-indicator-menuitem-sharing-application-with-n-tabs"
+ ),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ source,
+ "webrtcIndicator.sharingApplicationWithNTabs.menuitem",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-indicator-menuitem-sharing-screen-with-n-tabs"
+ ),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ source,
+ "webrtcIndicator.sharingScreenWithNTabs.menuitem",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-indicator-menuitem-sharing-window-with-n-tabs"
+ ),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ source,
+ "webrtcIndicator.sharingWindowWithNTabs.menuitem",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-indicator-menuitem-sharing-browser-with-n-tabs"
+ ),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ source,
+ "webrtcIndicator.sharingBrowserWithNTabs.menuitem",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-item-camera"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.sharingMenuCamera",
+ {
+ "%1$S (": FTL.TextElement(""),
+ "%1$S(": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-item-microphone"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.sharingMenuMicrophone",
+ {
+ "%1$S (": FTL.TextElement(""),
+ "%1$S(": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-item-audio-capture"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.sharingMenuAudioCapture",
+ {
+ "%1$S (": FTL.TextElement(""),
+ "%1$S(": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-item-application"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.sharingMenuApplication",
+ {
+ "%1$S (": FTL.TextElement(""),
+ "%1$S(": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-item-screen"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.sharingMenuScreen",
+ {
+ "%1$S (": FTL.TextElement(""),
+ "%1$S(": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-item-window"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.sharingMenuWindow",
+ {
+ "%1$S (": FTL.TextElement(""),
+ "%1$S(": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-item-browser"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.sharingMenuBrowser",
+ {
+ "%1$S (": FTL.TextElement(""),
+ "%1$S(": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ ")": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-sharing-menuitem-unknown-host"),
+ value=COPY(browser, "getUserMedia.sharingMenuUnknownHost"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-sharing-menuitem"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=FTL.Pattern(
+ [FTL.TextElement("{ $origin } ({ $itemList })")]
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-sharing-menu"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser, "getUserMedia.sharingMenu.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(browser, "getUserMedia.sharingMenu.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-camera"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareCamera3.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-microphone"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareMicrophone3.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-screen"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareScreen4.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-camera-and-microphone"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareCameraAndMicrophone3.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-camera-and-audio-capture"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareCameraAndAudioCapture3.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-screen-and-microphone"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareScreenAndMicrophone4.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-screen-and-audio-capture"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareScreenAndAudioCapture4.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-audio-capture"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareAudioCapture3.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-speaker"),
+ value=REPLACE(
+ browser,
+ "selectAudioOutput.shareSpeaker.message",
+ {"%1$S": VARIABLE_REFERENCE("origin")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-camera-unsafe-delegation"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareCameraUnsafeDelegation2.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-microphone-unsafe-delegations"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareMicrophoneUnsafeDelegations2.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-screen-unsafe-delegation"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareScreenUnsafeDelegation2.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-allow-share-camera-and-microphone-unsafe-delegation"
+ ),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareCameraAndMicrophoneUnsafeDelegation2.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-allow-share-camera-and-audio-capture-unsafe-delegation"
+ ),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareCameraAndAudioCaptureUnsafeDelegation2.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-allow-share-screen-and-microphone-unsafe-delegation"
+ ),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareScreenAndMicrophoneUnsafeDelegation2.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webrtc-allow-share-screen-and-audio-capture-unsafe-delegation"
+ ),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareScreenAndAudioCaptureUnsafeDelegation2.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-allow-share-speaker-unsafe-delegation"),
+ value=REPLACE(
+ browser,
+ "selectAudioOutput.shareSpeakerUnsafeDelegation.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("origin"),
+ "%2$S": VARIABLE_REFERENCE("thirdParty"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-share-screen-warning"),
+ value=COPY(browser, "getUserMedia.shareScreenWarning2.message"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-share-browser-warning"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareFirefoxWarning2.message",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-share-screen-learn-more"),
+ value=COPY(browser, "getUserMedia.shareScreen.learnMoreLabel"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-pick-window-or-screen"),
+ value=COPY(browser, "getUserMedia.pickWindowOrScreen.label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-share-entire-screen"),
+ value=COPY(browser, "getUserMedia.shareEntireScreen.label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-share-pipe-wire-portal"),
+ value=COPY(browser, "getUserMedia.sharePipeWirePortal.label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-share-monitor"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.shareMonitor.label",
+ {"%1$S": VARIABLE_REFERENCE("monitorIndex")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-share-application"),
+ value=PLURALS(
+ browser,
+ "getUserMedia.shareApplicationWindowCount.label",
+ VARIABLE_REFERENCE("windowCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": VARIABLE_REFERENCE("appName"),
+ "#2": VARIABLE_REFERENCE("windowCount"),
+ },
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-action-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser, "getUserMedia.allow.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(browser, "getUserMedia.allow.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-action-block"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY_PATTERN(
+ browser_ftl, "popup-screen-sharing-block.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY_PATTERN(
+ browser_ftl, "popup-screen-sharing-block.accesskey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-action-always-block"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY_PATTERN(
+ browser_ftl, "popup-screen-sharing-always-block.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY_PATTERN(
+ browser_ftl,
+ "popup-screen-sharing-always-block.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-action-not-now"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser, "getUserMedia.notNow.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(browser, "getUserMedia.notNow.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-remember-allow-checkbox"),
+ value=COPY(browser, "getUserMedia.remember"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-mute-notifications-checkbox"),
+ value=COPY_PATTERN(browser_ftl, "popup-mute-notifications-checkbox"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-reason-for-no-permanent-allow-screen"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.reasonForNoPermanentAllow.screen3",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-reason-for-no-permanent-allow-audio"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.reasonForNoPermanentAllow.audio",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webrtc-reason-for-no-permanent-allow-insecure"),
+ value=REPLACE(
+ browser,
+ "getUserMedia.reasonForNoPermanentAllow.insecure",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ ],
+ )
+
+ ctx.add_transforms(
+ browser_ftl,
+ browser_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("popup-select-window-or-screen"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser, "getUserMedia.selectWindowOrScreen2.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser, "getUserMedia.selectWindowOrScreen2.accesskey"
+ ),
+ ),
+ ],
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1812135_newtab_moz_toggle_labels.py b/python/l10n/fluent_migrations/bug_1812135_newtab_moz_toggle_labels.py
new file mode 100644
index 0000000000..27db47b0b5
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1812135_newtab_moz_toggle_labels.py
@@ -0,0 +1,30 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1812135 - Convert some newtab customiation panel strings to use a label, part {index}"""
+
+ translations_ftl = "browser/browser/newtab/newtab.ftl"
+
+ ctx.add_transforms(
+ translations_ftl,
+ translations_ftl,
+ transforms_from(
+ """
+newtab-custom-shortcuts-toggle =
+ .label = {COPY_PATTERN(from_path, "newtab-custom-shortcuts-title")}
+ .description = {COPY_PATTERN(from_path, "newtab-custom-shortcuts-subtitle")}
+newtab-custom-pocket-toggle =
+ .label = {COPY_PATTERN(from_path, "newtab-custom-pocket-title")}
+ .description = {COPY_PATTERN(from_path, "newtab-custom-pocket-subtitle")}
+newtab-custom-recent-toggle =
+ .label = {COPY_PATTERN(from_path, "newtab-custom-recent-title")}
+ .description = {COPY_PATTERN(from_path, "newtab-custom-recent-subtitle")}
+""",
+ from_path=translations_ftl,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1814969_contextualIdentity.py b/python/l10n/fluent_migrations/bug_1814969_contextualIdentity.py
new file mode 100644
index 0000000000..10a6bd5690
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1814969_contextualIdentity.py
@@ -0,0 +1,101 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.transforms import COPY, COPY_PATTERN
+
+
+def migrate(ctx):
+ """Bug 1814969 - Convert contextual identity service strings to Fluent, part {index}."""
+
+ source = "browser/chrome/browser/browser.properties"
+ alltabs = "browser/browser/allTabsMenu.ftl"
+ target = "toolkit/toolkit/global/contextual-identity.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("user-context-personal"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "userContextPersonal.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "userContextPersonal.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("user-context-work"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "userContextWork.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "userContextWork.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("user-context-banking"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "userContextBanking.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "userContextBanking.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("user-context-shopping"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "userContextShopping.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "userContextShopping.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("user-context-none"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "userContextNone.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "userContextNone.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("user-context-manage-containers"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY_PATTERN(
+ alltabs, "all-tabs-menu-manage-user-context.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY_PATTERN(
+ alltabs, "all-tabs-menu-manage-user-context.accesskey"
+ ),
+ ),
+ ],
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1844783_mozMessageBarShopping.py b/python/l10n/fluent_migrations/bug_1844783_mozMessageBarShopping.py
new file mode 100644
index 0000000000..468839693c
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1844783_mozMessageBarShopping.py
@@ -0,0 +1,45 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1844783 - Use new moz-message-bar in shopping components, part {index}."""
+ shopping_ftl = "browser/browser/shopping.ftl"
+ ctx.add_transforms(
+ shopping_ftl,
+ shopping_ftl,
+ transforms_from(
+ """
+shopping-message-bar-generic-error =
+ .heading = {COPY_PATTERN(from_path, "shopping-message-bar-generic-error-title2")}
+ .message = {COPY_PATTERN(from_path, "shopping-message-bar-generic-error-message")}
+
+shopping-message-bar-warning-not-enough-reviews =
+ .heading = {COPY_PATTERN(from_path, "shopping-message-bar-warning-not-enough-reviews-title")}
+ .message = {COPY_PATTERN(from_path, "shopping-message-bar-warning-not-enough-reviews-message2")}
+
+shopping-message-bar-warning-product-not-available =
+ .heading = {COPY_PATTERN(from_path, "shopping-message-bar-warning-product-not-available-title")}
+ .message = {COPY_PATTERN(from_path, "shopping-message-bar-warning-product-not-available-message2")}
+
+shopping-message-bar-thanks-for-reporting =
+ .heading = {COPY_PATTERN(from_path, "shopping-message-bar-thanks-for-reporting-title")}
+ .message = {COPY_PATTERN(from_path, "shopping-message-bar-thanks-for-reporting-message2")}
+
+shopping-message-bar-warning-product-not-available-reported =
+ .heading = {COPY_PATTERN(from_path, "shopping-message-bar-warning-product-not-available-reported-title2")}
+ .message = {COPY_PATTERN(from_path, "shopping-message-bar-warning-product-not-available-reported-message2")}
+
+shopping-message-bar-page-not-supported =
+ .heading = {COPY_PATTERN(from_path, "shopping-message-bar-page-not-supported-title")}
+ .message = {COPY_PATTERN(from_path, "shopping-message-bar-page-not-supported-message")}
+
+shopping-survey-thanks =
+ .heading = {COPY_PATTERN(from_path, "shopping-survey-thanks-message")}
+""",
+ from_path=shopping_ftl,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1844850_mozMessageBar_unifiedExtensionsPanel.py b/python/l10n/fluent_migrations/bug_1844850_mozMessageBar_unifiedExtensionsPanel.py
new file mode 100644
index 0000000000..b7a4c919eb
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1844850_mozMessageBar_unifiedExtensionsPanel.py
@@ -0,0 +1,36 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+
+from fluent.migrate import COPY_PATTERN
+
+
+def migrate(ctx):
+ """Bug 1844850 - Use moz-message-bar in the unified extensions panel, part {index}."""
+ unifiedExtensions_ftl = "browser/browser/unifiedExtensions.ftl"
+ ctx.add_transforms(
+ unifiedExtensions_ftl,
+ unifiedExtensions_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("unified-extensions-mb-quarantined-domain-message-3"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("heading"),
+ value=COPY_PATTERN(
+ unifiedExtensions_ftl,
+ "unified-extensions-mb-quarantined-domain-title",
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("message"),
+ value=COPY_PATTERN(
+ unifiedExtensions_ftl,
+ "unified-extensions-mb-quarantined-domain-message-2",
+ ),
+ ),
+ ],
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1845150_search_engine_notification.py b/python/l10n/fluent_migrations/bug_1845150_search_engine_notification.py
new file mode 100644
index 0000000000..125baa4c1f
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1845150_search_engine_notification.py
@@ -0,0 +1,32 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import re
+import fluent.syntax.ast as FTL
+from fluent.migrate.transforms import TransformPattern
+
+
+class STRIP_LABEL(TransformPattern):
+ # Used to remove `<label data-l10n-name="remove-search-engine-article">` from a string
+ def visit_TextElement(self, node):
+ node.value = re.sub(
+ '\s?<label data-l10n-name="remove-search-engine-article">.+?</label>\s?',
+ "",
+ node.value,
+ )
+ return node
+
+
+def migrate(ctx):
+ """Bug 1845150 - Use moz-message-bar instead of message-bar in notificationbox.js, part {index}."""
+ search_ftl = "browser/browser/search.ftl"
+ ctx.add_transforms(
+ search_ftl,
+ search_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("removed-search-engine-message2"),
+ value=STRIP_LABEL(search_ftl, "removed-search-engine-message"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1845727_mozSupportLink_to_toolkit.py b/python/l10n/fluent_migrations/bug_1845727_mozSupportLink_to_toolkit.py
new file mode 100644
index 0000000000..99c2c68581
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1845727_mozSupportLink_to_toolkit.py
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1845727 - move mozSupportLink l10n file to toolkit, part {index}."""
+ browser_ftl = "browser/browser/components/mozSupportLink.ftl"
+ toolkit_ftl = "toolkit/toolkit/global/mozSupportLink.ftl"
+ ctx.add_transforms(
+ toolkit_ftl,
+ toolkit_ftl,
+ transforms_from(
+ """
+moz-support-link-text = {COPY_PATTERN(from_path, "moz-support-link-text")}
+""",
+ from_path=browser_ftl,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1851877_mozMessageBar_aboutAddons_uninstallBar.py b/python/l10n/fluent_migrations/bug_1851877_mozMessageBar_aboutAddons_uninstallBar.py
new file mode 100644
index 0000000000..878cc01f60
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1851877_mozMessageBar_aboutAddons_uninstallBar.py
@@ -0,0 +1,41 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.transforms import TransformPattern
+
+
+class STRIP_SPAN(TransformPattern):
+ # Used to remove `<span data-l10n-name="addon-name"></span>` from a string
+ def visit_TextElement(self, node):
+ span_start = node.value.find('<span data-l10n-name="addon-name">')
+ span_end = node.value.find("</span>")
+ if span_start != -1 and span_end == -1:
+ node.value = node.value[:span_start]
+ elif span_start == -1 and span_end != -1:
+ node.value = node.value[span_end + 7 :]
+
+ return node
+
+
+def migrate(ctx):
+ """Bug 1851877 - Use moz-message-bar to replace the pending uninstall bar in about:addons, part {index}."""
+ aboutAddons_ftl = "toolkit/toolkit/about/aboutAddons.ftl"
+ ctx.add_transforms(
+ aboutAddons_ftl,
+ aboutAddons_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("pending-uninstall-description2"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("message"),
+ value=STRIP_SPAN(
+ aboutAddons_ftl,
+ "pending-uninstall-description",
+ ),
+ ),
+ ],
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1854425_default_agent.py b/python/l10n/fluent_migrations/bug_1854425_default_agent.py
new file mode 100644
index 0000000000..500eb1871b
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1854425_default_agent.py
@@ -0,0 +1,58 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import TERM_REFERENCE
+from fluent.migrate.transforms import REPLACE, COPY
+
+
+def migrate(ctx):
+ """Bug 1854425 - Migrate defaultagent_localized.ini to Fluent, part {index}."""
+
+ source = "browser/defaultagent/defaultagent_localized.ini"
+ target = "browser/browser/backgroundtasks/defaultagent.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("default-browser-agent-task-description"),
+ value=REPLACE(
+ source,
+ "DefaultBrowserAgentTaskDescription",
+ {
+ "%MOZ_APP_DISPLAYNAME%": TERM_REFERENCE("brand-short-name"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("default-browser-notification-header-text"),
+ value=REPLACE(
+ source,
+ "DefaultBrowserNotificationHeaderText",
+ {
+ "%MOZ_APP_DISPLAYNAME%": TERM_REFERENCE("brand-short-name"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("default-browser-notification-body-text"),
+ value=REPLACE(
+ source,
+ "DefaultBrowserNotificationBodyText",
+ {
+ "%MOZ_APP_DISPLAYNAME%": TERM_REFERENCE("brand-short-name"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("default-browser-notification-yes-button-text"),
+ value=COPY(source, "DefaultBrowserNotificationYesButtonText"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("default-browser-notification-no-button-text"),
+ value=COPY(source, "DefaultBrowserNotificationNoButtonText"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1856014_protectionsPanel_fixButtonLabels.py b/python/l10n/fluent_migrations/bug_1856014_protectionsPanel_fixButtonLabels.py
new file mode 100644
index 0000000000..d5541e91de
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1856014_protectionsPanel_fixButtonLabels.py
@@ -0,0 +1,36 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1856014 - Fix fluent schema for strings in protections panel, part {index}."""
+
+ file = "browser/browser/protectionsPanel.ftl"
+ ctx.add_transforms(
+ file,
+ file,
+ transforms_from(
+ """
+protections-panel-not-blocking-why-etp-on-tooltip-label =
+ .label = {COPY_PATTERN(from_path, "protections-panel-not-blocking-why-etp-on-tooltip")}
+protections-panel-not-blocking-why-etp-off-tooltip-label =
+ .label = {COPY_PATTERN(from_path, "protections-panel-not-blocking-why-etp-off-tooltip")}
+""",
+ from_path=file,
+ ),
+ )
+ ctx.add_transforms(
+ file,
+ file,
+ transforms_from(
+ """
+protections-panel-cookie-banner-view-turn-off-label =
+ .label = {COPY_PATTERN(from_path, "protections-panel-cookie-banner-view-turn-off")}
+protections-panel-cookie-banner-view-turn-on-label =
+ .label = {COPY_PATTERN(from_path, "protections-panel-cookie-banner-view-turn-on")}
+""",
+ from_path=file,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1858715_pdfjs.py b/python/l10n/fluent_migrations/bug_1858715_pdfjs.py
new file mode 100644
index 0000000000..71b4c592f4
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1858715_pdfjs.py
@@ -0,0 +1,1078 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import VARIABLE_REFERENCE
+from fluent.migrate.transforms import COPY, REPLACE
+
+
+def migrate(ctx):
+ """Bug 1858715 - Convert viewer.properties to Fluent, part {index}."""
+
+ source = "browser/pdfviewer/viewer.properties"
+ target = "toolkit/toolkit/pdfviewer/viewer.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-previous-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "previous.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-previous-button-label"),
+ value=COPY(source, "previous_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-next-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "next.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-next-button-label"),
+ value=COPY(source, "next_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-input"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "page.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-of-pages"),
+ value=REPLACE(
+ source,
+ "of_pages",
+ {"{{pagesCount}}": VARIABLE_REFERENCE("pagesCount")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-of-pages"),
+ value=REPLACE(
+ source,
+ "page_of_pages",
+ {
+ "{{pageNumber}}": VARIABLE_REFERENCE("pageNumber"),
+ "{{pagesCount}}": VARIABLE_REFERENCE("pagesCount"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-zoom-out-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "zoom_out.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-zoom-out-button-label"),
+ value=COPY(source, "zoom_out_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-zoom-in-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "zoom_in.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-zoom-in-button-label"),
+ value=COPY(source, "zoom_in_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-zoom-select"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "zoom.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-presentation-mode-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "presentation_mode.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-presentation-mode-button-label"),
+ value=COPY(source, "presentation_mode_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-open-file-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "open_file.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-open-file-button-label"),
+ value=COPY(source, "open_file_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-print-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "print.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-print-button-label"),
+ value=COPY(source, "print_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-save-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "save.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-save-button-label"),
+ value=COPY(source, "save_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-download-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "download_button.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-download-button-label"),
+ value=COPY(source, "download_button_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-bookmark-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "bookmark1.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-bookmark-button-label"),
+ value=COPY(source, "bookmark1_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-open-in-app-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "open_in_app.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-open-in-app-button-label"),
+ value=COPY(source, "open_in_app_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-tools-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "tools.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-tools-button-label"),
+ value=COPY(source, "tools_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-first-page-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "first_page.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-first-page-button-label"),
+ value=COPY(source, "first_page_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-last-page-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "last_page.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-last-page-button-label"),
+ value=COPY(source, "last_page_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-rotate-cw-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "page_rotate_cw.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-rotate-cw-button-label"),
+ value=COPY(source, "page_rotate_cw_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-rotate-ccw-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "page_rotate_ccw.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-rotate-ccw-button-label"),
+ value=COPY(source, "page_rotate_ccw_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-cursor-text-select-tool-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "cursor_text_select_tool.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-cursor-text-select-tool-button-label"),
+ value=COPY(source, "cursor_text_select_tool_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-cursor-hand-tool-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "cursor_hand_tool.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-cursor-hand-tool-button-label"),
+ value=COPY(source, "cursor_hand_tool_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-page-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "scroll_page.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-page-button-label"),
+ value=COPY(source, "scroll_page_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-vertical-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "scroll_vertical.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-vertical-button-label"),
+ value=COPY(source, "scroll_vertical_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-horizontal-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "scroll_horizontal.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-horizontal-button-label"),
+ value=COPY(source, "scroll_horizontal_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-wrapped-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "scroll_wrapped.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-scroll-wrapped-button-label"),
+ value=COPY(source, "scroll_wrapped_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-spread-none-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "spread_none.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-spread-none-button-label"),
+ value=COPY(source, "spread_none_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-spread-odd-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "spread_odd.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-spread-odd-button-label"),
+ value=COPY(source, "spread_odd_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-spread-even-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "spread_even.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-spread-even-button-label"),
+ value=COPY(source, "spread_even_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "document_properties.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-button-label"),
+ value=COPY(source, "document_properties_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-file-name"),
+ value=COPY(source, "document_properties_file_name"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-file-size"),
+ value=COPY(source, "document_properties_file_size"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-kb"),
+ value=REPLACE(
+ source,
+ "document_properties_kb",
+ {
+ "{{size_kb}}": VARIABLE_REFERENCE("size_kb"),
+ "{{size_b}}": VARIABLE_REFERENCE("size_b"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-mb"),
+ value=REPLACE(
+ source,
+ "document_properties_mb",
+ {
+ "{{size_mb}}": VARIABLE_REFERENCE("size_mb"),
+ "{{size_b}}": VARIABLE_REFERENCE("size_b"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-title"),
+ value=COPY(source, "document_properties_title"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-author"),
+ value=COPY(source, "document_properties_author"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-subject"),
+ value=COPY(source, "document_properties_subject"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-keywords"),
+ value=COPY(source, "document_properties_keywords"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-creation-date"),
+ value=COPY(source, "document_properties_creation_date"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-modification-date"),
+ value=COPY(source, "document_properties_modification_date"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-date-string"),
+ value=REPLACE(
+ source,
+ "document_properties_date_string",
+ {
+ "{{date}}": VARIABLE_REFERENCE("date"),
+ "{{time}}": VARIABLE_REFERENCE("time"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-creator"),
+ value=COPY(source, "document_properties_creator"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-producer"),
+ value=COPY(source, "document_properties_producer"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-version"),
+ value=COPY(source, "document_properties_version"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-page-count"),
+ value=COPY(source, "document_properties_page_count"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-page-size"),
+ value=COPY(source, "document_properties_page_size"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-page-size-unit-inches"),
+ value=COPY(source, "document_properties_page_size_unit_inches"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "pdfjs-document-properties-page-size-unit-millimeters"
+ ),
+ value=COPY(source, "document_properties_page_size_unit_millimeters"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "pdfjs-document-properties-page-size-orientation-portrait"
+ ),
+ value=COPY(
+ source, "document_properties_page_size_orientation_portrait"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "pdfjs-document-properties-page-size-orientation-landscape"
+ ),
+ value=COPY(
+ source, "document_properties_page_size_orientation_landscape"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-page-size-name-a-three"),
+ value=COPY(source, "document_properties_page_size_name_a3"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-page-size-name-a-four"),
+ value=COPY(source, "document_properties_page_size_name_a4"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-page-size-name-letter"),
+ value=COPY(source, "document_properties_page_size_name_letter"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-page-size-name-legal"),
+ value=COPY(source, "document_properties_page_size_name_legal"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "pdfjs-document-properties-page-size-dimension-string"
+ ),
+ value=REPLACE(
+ source,
+ "document_properties_page_size_dimension_string",
+ {
+ "{{width}}": VARIABLE_REFERENCE("width"),
+ "{{height}}": VARIABLE_REFERENCE("height"),
+ "{{unit}}": VARIABLE_REFERENCE("unit"),
+ "{{orientation}}": VARIABLE_REFERENCE("orientation"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "pdfjs-document-properties-page-size-dimension-name-string"
+ ),
+ value=REPLACE(
+ source,
+ "document_properties_page_size_dimension_name_string",
+ {
+ "{{width}}": VARIABLE_REFERENCE("width"),
+ "{{height}}": VARIABLE_REFERENCE("height"),
+ "{{unit}}": VARIABLE_REFERENCE("unit"),
+ "{{name}}": VARIABLE_REFERENCE("name"),
+ "{{orientation}}": VARIABLE_REFERENCE("orientation"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-linearized"),
+ value=COPY(source, "document_properties_linearized"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-linearized-yes"),
+ value=COPY(source, "document_properties_linearized_yes"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-linearized-no"),
+ value=COPY(source, "document_properties_linearized_no"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-properties-close-button"),
+ value=COPY(source, "document_properties_close"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-print-progress-message"),
+ value=COPY(source, "print_progress_message"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-print-progress-percent"),
+ value=REPLACE(
+ source,
+ "print_progress_percent",
+ {
+ "{{progress}}": VARIABLE_REFERENCE("progress"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-print-progress-close-button"),
+ value=COPY(source, "print_progress_close"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-toggle-sidebar-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "toggle_sidebar.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-toggle-sidebar-notification-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "toggle_sidebar_notification2.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-toggle-sidebar-button-label"),
+ value=COPY(source, "toggle_sidebar_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-outline-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "document_outline.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-document-outline-button-label"),
+ value=COPY(source, "document_outline_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-attachments-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "attachments.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-attachments-button-label"),
+ value=COPY(source, "attachments_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-layers-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "layers.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-layers-button-label"),
+ value=COPY(source, "layers_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-thumbs-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "thumbs.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-thumbs-button-label"),
+ value=COPY(source, "thumbs_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-current-outline-item-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "current_outline_item.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-current-outline-item-button-label"),
+ value=COPY(source, "current_outline_item_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-findbar-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"), value=COPY(source, "findbar.title")
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-findbar-button-label"),
+ value=COPY(source, "findbar_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-additional-layers"),
+ value=COPY(source, "additional_layers"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-landmark"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("aria-label"),
+ value=REPLACE(
+ source,
+ "page_landmark",
+ {
+ "{{page}}": VARIABLE_REFERENCE("page"),
+ },
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-thumb-page-title"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=REPLACE(
+ source,
+ "thumb_page_title",
+ {
+ "{{page}}": VARIABLE_REFERENCE("page"),
+ },
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-thumb-page-canvas"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("aria-label"),
+ value=REPLACE(
+ source,
+ "thumb_page_canvas",
+ {
+ "{{page}}": VARIABLE_REFERENCE("page"),
+ },
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-input"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "find_input.title"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("placeholder"),
+ value=COPY(source, "find_input.placeholder"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-previous-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "find_previous.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-previous-button-label"),
+ value=COPY(source, "find_previous_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-next-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "find_next.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-next-button-label"),
+ value=COPY(source, "find_next_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-highlight-checkbox"),
+ value=COPY(source, "find_highlight"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-match-case-checkbox-label"),
+ value=COPY(source, "find_match_case_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-match-diacritics-checkbox-label"),
+ value=COPY(source, "find_match_diacritics_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-entire-word-checkbox-label"),
+ value=COPY(source, "find_entire_word_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-reached-top"),
+ value=COPY(source, "find_reached_top"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-reached-bottom"),
+ value=COPY(source, "find_reached_bottom"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-find-not-found"),
+ value=COPY(source, "find_not_found"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-scale-width"),
+ value=COPY(source, "page_scale_width"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-scale-fit"),
+ value=COPY(source, "page_scale_fit"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-scale-auto"),
+ value=COPY(source, "page_scale_auto"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-scale-actual"),
+ value=COPY(source, "page_scale_actual"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-page-scale-percent"),
+ value=REPLACE(
+ source,
+ "page_scale_percent",
+ {
+ "{{scale}}": VARIABLE_REFERENCE("scale"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-loading-error"),
+ value=COPY(source, "loading_error"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-invalid-file-error"),
+ value=COPY(source, "invalid_file_error"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-missing-file-error"),
+ value=COPY(source, "missing_file_error"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-unexpected-response-error"),
+ value=COPY(source, "unexpected_response_error"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-rendering-error"),
+ value=COPY(source, "rendering_error"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-annotation-date-string"),
+ value=REPLACE(
+ source,
+ "annotation_date_string",
+ {
+ "{{date}}": VARIABLE_REFERENCE("date"),
+ "{{time}}": VARIABLE_REFERENCE("time"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-text-annotation-type"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("alt"),
+ value=REPLACE(
+ source,
+ "text_annotation_type.alt",
+ {
+ "{{type}}": VARIABLE_REFERENCE("type"),
+ },
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-password-label"),
+ value=COPY(source, "password_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-password-invalid"),
+ value=COPY(source, "password_invalid"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-password-ok-button"),
+ value=COPY(source, "password_ok"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-password-cancel-button"),
+ value=COPY(source, "password_cancel"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-printing-not-supported"),
+ value=COPY(source, "printing_not_supported"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-printing-not-ready"),
+ value=COPY(source, "printing_not_ready"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-web-fonts-disabled"),
+ value=COPY(source, "web_fonts_disabled"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-free-text-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "editor_free_text2.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-free-text-button-label"),
+ value=COPY(source, "editor_free_text2_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-ink-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "editor_ink2.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-ink-button-label"),
+ value=COPY(source, "editor_ink2_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-stamp-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "editor_stamp1.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-stamp-button-label"),
+ value=COPY(source, "editor_stamp1_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-free-text-default-content"),
+ value=COPY(source, "free_text2_default_content"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-free-text-color-input"),
+ value=COPY(source, "editor_free_text_color"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-free-text-size-input"),
+ value=COPY(source, "editor_free_text_size"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-ink-color-input"),
+ value=COPY(source, "editor_ink_color"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-ink-thickness-input"),
+ value=COPY(source, "editor_ink_thickness"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-ink-opacity-input"),
+ value=COPY(source, "editor_ink_opacity"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-stamp-add-image-button-label"),
+ value=COPY(source, "editor_stamp_add_image_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-stamp-add-image-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY(source, "editor_stamp_add_image.title"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-free-text"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("aria-label"),
+ value=COPY(source, "editor_free_text2_aria_label"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-ink"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("aria-label"),
+ value=COPY(source, "editor_ink2_aria_label"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-ink-canvas"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("aria-label"),
+ value=COPY(source, "editor_ink_canvas_aria_label"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-button-label"),
+ value=COPY(source, "editor_alt_text_button_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-edit-button-label"),
+ value=COPY(source, "editor_alt_text_edit_button_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-dialog-label"),
+ value=COPY(source, "editor_alt_text_dialog_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-dialog-description"),
+ value=COPY(source, "editor_alt_text_dialog_description"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-add-description-label"),
+ value=COPY(source, "editor_alt_text_add_description_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-add-description-description"),
+ value=COPY(source, "editor_alt_text_add_description_description"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-mark-decorative-label"),
+ value=COPY(source, "editor_alt_text_mark_decorative_label"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-mark-decorative-description"),
+ value=COPY(source, "editor_alt_text_mark_decorative_description"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-cancel-button"),
+ value=COPY(source, "editor_alt_text_cancel_button"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-save-button"),
+ value=COPY(source, "editor_alt_text_save_button"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-decorative-tooltip"),
+ value=COPY(source, "editor_alt_text_decorative_tooltip"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-alt-text-textarea"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("placeholder"),
+ value=COPY(source, "editor_alt_text_textarea.placeholder"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-top-left"),
+ value=COPY(source, "editor_resizer_label_topLeft"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-top-middle"),
+ value=COPY(source, "editor_resizer_label_topMiddle"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-top-right"),
+ value=COPY(source, "editor_resizer_label_topRight"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-middle-right"),
+ value=COPY(source, "editor_resizer_label_middleRight"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-bottom-right"),
+ value=COPY(source, "editor_resizer_label_bottomRight"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-bottom-middle"),
+ value=COPY(source, "editor_resizer_label_bottomMiddle"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-bottom-left"),
+ value=COPY(source, "editor_resizer_label_bottomLeft"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("pdfjs-editor-resizer-label-middle-left"),
+ value=COPY(source, "editor_resizer_label_middleLeft"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1860606_remove_migration_ftl.py b/python/l10n/fluent_migrations/bug_1860606_remove_migration_ftl.py
new file mode 100644
index 0000000000..3ad99a87b2
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1860606_remove_migration_ftl.py
@@ -0,0 +1,27 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1860606 - Get rid of migration.ftl in favour of migrationWizard.ftl, part {index}."""
+
+ source = "browser/browser/migration.ftl"
+ target = "browser/browser/migrationWizard.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ transforms_from(
+ """
+migration-source-name-ie = {COPY_PATTERN(from_path, "source-name-ie")}
+migration-source-name-edge = {COPY_PATTERN(from_path, "source-name-edge")}
+migration-source-name-chrome = {COPY_PATTERN(from_path, "source-name-chrome")}
+
+migration-imported-safari-reading-list = {COPY_PATTERN(from_path, "imported-safari-reading-list")}
+migration-imported-edge-reading-list = {COPY_PATTERN(from_path, "imported-edge-reading-list")}
+""",
+ from_path=source,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1862982_protectionsPanel_fixCancelButtonLabel.py b/python/l10n/fluent_migrations/bug_1862982_protectionsPanel_fixCancelButtonLabel.py
new file mode 100644
index 0000000000..cb5c87da42
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1862982_protectionsPanel_fixCancelButtonLabel.py
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1862982 - Fix fluent schema for cookie banner cancel button, part {index}."""
+
+ file = "browser/browser/protectionsPanel.ftl"
+ ctx.add_transforms(
+ file,
+ file,
+ transforms_from(
+ """
+protections-panel-cookie-banner-view-cancel-label =
+ .label = {COPY_PATTERN(from_path, "protections-panel-cookie-banner-view-cancel")}
+""",
+ from_path=file,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1863022_protectionsPanel_infomessage.py b/python/l10n/fluent_migrations/bug_1863022_protectionsPanel_infomessage.py
new file mode 100644
index 0000000000..b09d362475
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1863022_protectionsPanel_infomessage.py
@@ -0,0 +1,24 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1863022 - Move Protection Panel Message to calling code, part {index}"""
+
+ source = "browser/browser/newtab/asrouter.ftl"
+ target = "browser/browser/protectionsPanel.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ transforms_from(
+ """
+cfr-protections-panel-header = {COPY_PATTERN(from_path, "cfr-protections-panel-header")}
+cfr-protections-panel-body = {COPY_PATTERN(from_path, "cfr-protections-panel-body")}
+cfr-protections-panel-link-text = {COPY_PATTERN(from_path, "cfr-protections-panel-link-text")}
+""",
+ from_path=source,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1866268_geckoViewConsole.py b/python/l10n/fluent_migrations/bug_1866268_geckoViewConsole.py
new file mode 100644
index 0000000000..93cf2065be
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1866268_geckoViewConsole.py
@@ -0,0 +1,52 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import VARIABLE_REFERENCE
+from fluent.migrate.transforms import COPY, REPLACE
+
+
+def migrate(ctx):
+ """Bug 1866268 - Convert GeckoViewConsole strings to Fluent, part {index}."""
+
+ source = "mobile/android/chrome/browser.properties"
+ target = "mobile/android/mobile/android/geckoViewConsole.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("console-stacktrace-anonymous-function"),
+ value=COPY(source, "stacktrace.anonymousFunction"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("console-stacktrace"),
+ value=REPLACE(
+ source,
+ "stacktrace.outputMessage",
+ {
+ "%1$S": VARIABLE_REFERENCE("filename"),
+ "%2$S": VARIABLE_REFERENCE("functionName"),
+ "%3$S": VARIABLE_REFERENCE("lineNumber"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("console-timer-start"),
+ value=REPLACE(
+ source, "timer.start", {"%1$S": VARIABLE_REFERENCE("name")}
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("console-timer-end"),
+ value=REPLACE(
+ source,
+ "timer.end",
+ {
+ "%1$S": VARIABLE_REFERENCE("name"),
+ "%2$S": VARIABLE_REFERENCE("duration"),
+ },
+ ),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1866295_new_device_migration_strings.py b/python/l10n/fluent_migrations/bug_1866295_new_device_migration_strings.py
new file mode 100644
index 0000000000..a8ccb8c145
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1866295_new_device_migration_strings.py
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1866295 - Land new strings for device migration ASRouter messages, part {index}."""
+
+ source = "browser/browser/newtab/asrouter.ftl"
+ target = source
+
+ ctx.add_transforms(
+ target,
+ target,
+ transforms_from(
+ """
+device-migration-fxa-spotlight-getting-new-device-primary-button = {COPY_PATTERN(from_path, "device-migration-fxa-spotlight-primary-button")}
+""",
+ from_path=source,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1867346_new_device_migration_string_replacement.py b/python/l10n/fluent_migrations/bug_1867346_new_device_migration_string_replacement.py
new file mode 100644
index 0000000000..a926bff41a
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1867346_new_device_migration_string_replacement.py
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1867346 - Replace a string for device migration ASRouter messages, part {index}."""
+
+ source = "browser/browser/newtab/asrouter.ftl"
+ target = source
+
+ ctx.add_transforms(
+ target,
+ target,
+ transforms_from(
+ """
+device-migration-fxa-spotlight-getting-new-device-header-2 = {COPY_PATTERN(from_path, "fxa-sync-cfr-header")}
+""",
+ from_path=source,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1867819_formautofill.py b/python/l10n/fluent_migrations/bug_1867819_formautofill.py
new file mode 100644
index 0000000000..1b4d9606f1
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1867819_formautofill.py
@@ -0,0 +1,26 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.transforms import COPY
+
+
+def migrate(ctx):
+ """Bug 1867819 - Convert formautofill.properties to Fluent, part {index}."""
+
+ source = "browser/extensions/formautofill/formautofill.properties"
+ target = "toolkit/toolkit/formautofill/formAutofill.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("autofill-options-link"),
+ value=COPY(source, "autofillOptionsLink"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("autofill-options-link-osx"),
+ value=COPY(source, "autofillOptionsLinkOSX"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1868154_RFPHelper.py b/python/l10n/fluent_migrations/bug_1868154_RFPHelper.py
new file mode 100644
index 0000000000..e0e0345ab0
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1868154_RFPHelper.py
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.transforms import COPY
+
+
+def migrate(ctx):
+ """Bug 1868154 - Convert RFPHelper strings to Fluent, part {index}."""
+
+ source = "browser/chrome/browser/browser.properties"
+ target = "toolkit/toolkit/global/resistFingerPrinting.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("privacy-spoof-english"),
+ value=COPY(source, "privacy.spoof_english"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1869024_passwordmgr.py b/python/l10n/fluent_migrations/bug_1869024_passwordmgr.py
new file mode 100644
index 0000000000..2cfe7151dc
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1869024_passwordmgr.py
@@ -0,0 +1,122 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migrate.transforms import COPY, REPLACE
+from fluent.migrate.helpers import VARIABLE_REFERENCE
+
+
+def migrate(ctx):
+ """Bug 1869024 - Convert passwordmgr.properties to Fluent, part {index}."""
+
+ source = "toolkit/chrome/passwordmgr/passwordmgr.properties"
+ target = "toolkit/toolkit/passwordmgr/passwordmgr.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("password-manager-save-password-button-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "saveLoginButtonAllow.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "saveLoginButtonAllow.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-save-password-button-never"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "saveLoginButtonNever.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "saveLoginButtonNever.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-update-login-add-username"),
+ value=COPY(source, "updateLoginMsgAddUsername2"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-password-password-button-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "updateLoginButtonText"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "updateLoginButtonAccessKey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-update-password-button-deny"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "updateLoginButtonDeny.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "updateLoginButtonDeny.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-no-username-placeholder"),
+ value=COPY(source, "noUsernamePlaceholder"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-toggle-password"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "togglePasswordLabel"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "togglePasswordAccessKey2"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-confirm-password-change"),
+ value=COPY(source, "passwordChangeTitle"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-select-username"),
+ value=COPY(source, "userSelectText2"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-save-password-message"),
+ value=REPLACE(
+ source,
+ "saveLoginMsgNoUser2",
+ {
+ "%1$S": VARIABLE_REFERENCE("host"),
+ },
+ normalize_printf=True,
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("password-manager-update-password-message"),
+ value=REPLACE(
+ source,
+ "updateLoginMsgNoUser3",
+ {
+ "%1$S": VARIABLE_REFERENCE("host"),
+ },
+ normalize_printf=True,
+ ),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1869389_urlbar_search_mode_indicator_close_button_addAccessibleName.py b/python/l10n/fluent_migrations/bug_1869389_urlbar_search_mode_indicator_close_button_addAccessibleName.py
new file mode 100644
index 0000000000..7374e1e0f4
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1869389_urlbar_search_mode_indicator_close_button_addAccessibleName.py
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1869389 - Provide urlbar-search-mode-indicator-close button with an accessible name, part {index}"""
+
+ file = "browser/browser/browser.ftl"
+ ctx.add_transforms(
+ file,
+ file,
+ transforms_from(
+ """
+urlbar-search-mode-indicator-close =
+ .aria-label = {COPY_PATTERN(from_path, "ui-tour-info-panel-close.tooltiptext")}
+""",
+ from_path=file,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_747301_about_plugin_removal.py b/python/l10n/fluent_migrations/bug_747301_about_plugin_removal.py
new file mode 100644
index 0000000000..a6df49f3ef
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_747301_about_plugin_removal.py
@@ -0,0 +1,28 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 747301 - remove about:plugins, part {index}."""
+ plugins_ftl = "toolkit/toolkit/about/aboutPlugins.ftl"
+ addon_ftl = "toolkit/toolkit/about/aboutAddons.ftl"
+ ctx.add_transforms(
+ addon_ftl,
+ addon_ftl,
+ transforms_from(
+ """
+plugins-gmp-license-info = {COPY_PATTERN(from_path, "plugins-gmp-license-info")}
+plugins-gmp-privacy-info = {COPY_PATTERN(from_path, "plugins-gmp-privacy-info")}
+
+plugins-openh264-name = {COPY_PATTERN(from_path, "plugins-openh264-name")}
+plugins-openh264-description = {COPY_PATTERN(from_path, "plugins-openh264-description")}
+
+plugins-widevine-name = {COPY_PATTERN(from_path, "plugins-widevine-name")}
+plugins-widevine-description = {COPY_PATTERN(from_path, "plugins-widevine-description")}
+""",
+ from_path=plugins_ftl,
+ ),
+ )
diff --git a/python/l10n/mozxchannel/__init__.py b/python/l10n/mozxchannel/__init__.py
new file mode 100644
index 0000000000..a531cdcaab
--- /dev/null
+++ b/python/l10n/mozxchannel/__init__.py
@@ -0,0 +1,150 @@
+# 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/.
+
+import os
+import shutil
+from collections import defaultdict
+from dataclasses import dataclass, field
+from datetime import datetime
+from pathlib import Path
+
+import hglib
+from compare_locales import merge
+from mozpack import path as mozpath
+
+from . import projectconfig, source
+
+
+def get_default_config(topsrcdir, strings_path):
+ assert isinstance(topsrcdir, Path)
+ assert isinstance(strings_path, Path)
+ return {
+ "strings": {
+ "path": strings_path,
+ "url": "https://hg.mozilla.org/l10n/gecko-strings-quarantine/",
+ "heads": {"default": "default"},
+ "update_on_pull": True,
+ "push_url": "ssh://hg.mozilla.org/l10n/gecko-strings-quarantine/",
+ },
+ "source": {
+ "mozilla-unified": {
+ "path": topsrcdir,
+ "url": "https://hg.mozilla.org/mozilla-unified/",
+ "heads": {
+ # This list of repositories is ordered, starting with the
+ # one with the most recent content (central) to the oldest
+ # (ESR). In case two ESR versions are supported, the oldest
+ # ESR goes last (e.g. esr78 goes after esr91).
+ "central": "mozilla-central",
+ "beta": "releases/mozilla-beta",
+ "release": "releases/mozilla-release",
+ "esr115": "releases/mozilla-esr115",
+ },
+ "config_files": [
+ "browser/locales/l10n.toml",
+ "mobile/android/locales/l10n.toml",
+ ],
+ },
+ },
+ }
+
+
+@dataclass
+class TargetRevs:
+ target: bytes = None
+ revs: list = field(default_factory=list)
+
+
+@dataclass
+class CommitRev:
+ repo: str
+ rev: bytes
+
+ @property
+ def message(self):
+ return (
+ f"X-Channel-Repo: {self.repo}\n"
+ f'X-Channel-Revision: {self.rev.decode("ascii")}'
+ )
+
+
+class CrossChannelCreator:
+ def __init__(self, config):
+ self.config = config
+ self.strings_path = config["strings"]["path"]
+ self.message = (
+ f"cross-channel content for {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}"
+ )
+
+ def create_content(self):
+ self.prune_target()
+ revs = []
+ for repo_name, repo_config in self.config["source"].items():
+ with hglib.open(repo_config["path"]) as repo:
+ revs.extend(self.create_for_repo(repo, repo_name, repo_config))
+ self.commit(revs)
+ return 0
+
+ def prune_target(self):
+ for leaf in self.config["strings"]["path"].iterdir():
+ if leaf.name == ".hg":
+ continue
+ shutil.rmtree(leaf)
+
+ def create_for_repo(self, repo, repo_name, repo_config):
+ print(f"Processing {repo_name} in {repo_config['path']}")
+ source_target_revs = defaultdict(TargetRevs)
+ revs_for_commit = []
+ parse_kwargs = {
+ "env": {"l10n_base": str(self.strings_path.parent)},
+ "ignore_missing_includes": True,
+ }
+ for head, head_name in repo_config["heads"].items():
+ print(f"Gathering files for {head}")
+ rev = repo.log(revrange=head)[0].node
+ revs_for_commit.append(CommitRev(head_name, rev))
+ p = source.HgTOMLParser(repo, rev)
+ project_configs = []
+ for config_file in repo_config["config_files"]:
+ project_configs.append(p.parse(config_file, **parse_kwargs))
+ project_configs[-1].set_locales(["en-US"], deep=True)
+ hgfiles = source.HGFiles(repo, rev, project_configs)
+ for targetpath, refpath, _, _ in hgfiles:
+ source_target_revs[refpath].revs.append(rev)
+ source_target_revs[refpath].target = targetpath
+ root = repo.root()
+ print(f"Writing {repo_name} content to target")
+ for refpath, targetrevs in source_target_revs.items():
+ local_ref = mozpath.relpath(refpath, root)
+ content = self.get_content(local_ref, repo, targetrevs.revs)
+ target_dir = mozpath.dirname(targetrevs.target)
+ if not os.path.isdir(target_dir):
+ os.makedirs(target_dir)
+ with open(targetrevs.target, "wb") as fh:
+ fh.write(content)
+ return revs_for_commit
+
+ def commit(self, revs):
+ message = self.message + "\n\n"
+ if "TASK_ID" in os.environ:
+ message += f"X-Task-ID: {os.environ['TASK_ID']}\n\n"
+ message += "\n".join(rev.message for rev in revs)
+ with hglib.open(self.strings_path) as repo:
+ repo.commit(message=message, addremove=True)
+
+ def get_content(self, local_ref, repo, revs):
+ if local_ref.endswith(b".toml"):
+ return self.get_config_content(local_ref, repo, revs)
+ if len(revs) < 2:
+ return repo.cat([b"path:" + local_ref], rev=revs[0])
+ contents = [repo.cat([b"path:" + local_ref], rev=rev) for rev in revs]
+ try:
+ return merge.merge_channels(local_ref.decode("utf-8"), contents)
+ except merge.MergeNotSupportedError:
+ return contents[0]
+
+ def get_config_content(self, local_ref, repo, revs):
+ # We don't support merging toml files
+ content = repo.cat([b"path:" + local_ref], rev=revs[0])
+ return projectconfig.process_config(content)
diff --git a/python/l10n/mozxchannel/projectconfig.py b/python/l10n/mozxchannel/projectconfig.py
new file mode 100644
index 0000000000..8551546541
--- /dev/null
+++ b/python/l10n/mozxchannel/projectconfig.py
@@ -0,0 +1,77 @@
+# 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/.
+
+import re
+
+from compare_locales import mozpath
+
+# The local path where we write the config files to
+TARGET_PATH = b"_configs"
+
+
+def process_config(toml_content):
+ """Process TOML configuration content to match the l10n setup for
+ the reference localization, return target_path and content.
+
+ The code adjusts basepath, [[paths]], and [[includes]]
+ """
+ # adjust basepath in content. '.' works in practice, also in theory?
+ new_base = mozpath.relpath(b".", TARGET_PATH)
+ if not new_base:
+ new_base = b"." # relpath to '.' is '', sadly
+ base_line = b'\nbasepath = "%s"' % new_base
+ content1 = re.sub(rb"^\s*basepath\s*=\s*.+", base_line, toml_content, flags=re.M)
+
+ # process [[paths]]
+ start = 0
+ content2 = b""
+ for m in re.finditer(
+ rb"\[\[\s*paths\s*\]\].+?(?=\[|\Z)", content1, re.M | re.DOTALL
+ ):
+ content2 += content1[start : m.start()]
+ path_content = m.group()
+ l10n_line = re.search(rb"^\s*l10n\s*=.*$", path_content, flags=re.M).group()
+ # remove variable expansions
+ new_reference = re.sub(rb"{\s*\S+\s*}", b"", l10n_line)
+ # make the l10n a reference line
+ new_reference = re.sub(rb"^(\s*)l10n(\s*=)", rb"\1reference\2", new_reference)
+ content2 += re.sub(
+ rb"^\s*reference\s*=.*$", new_reference, path_content, flags=re.M
+ )
+ start = m.end()
+ content2 += content1[start:]
+
+ start = 0
+ content3 = b""
+ for m in re.finditer(
+ rb"\[\[\s*includes\s*\]\].+?(?=\[|\Z)", content2, re.M | re.DOTALL
+ ):
+ content3 += content2[start : m.start()]
+ include_content = m.group()
+ m_ = re.search(rb'^\s*path = "(.+?)"', include_content, flags=re.M)
+ content3 += (
+ include_content[: m_.start(1)]
+ + generate_filename(m_.group(1))
+ + include_content[m_.end(1) :]
+ )
+ start = m.end()
+ content3 += content2[start:]
+
+ return content3
+
+
+def generate_filename(path):
+ segs = path.split(b"/")
+ # strip /locales/ from filename
+ segs = [seg for seg in segs if seg != b"locales"]
+ # strip variables from filename
+ segs = [seg for seg in segs if not seg.startswith(b"{") and not seg.endswith(b"}")]
+ if segs[-1] == b"l10n.toml":
+ segs.pop()
+ segs[-1] += b".toml"
+ outpath = b"-".join(segs)
+ if TARGET_PATH != b".":
+ # prepend the target path, if it's not '.'
+ outpath = mozpath.join(TARGET_PATH, outpath)
+ return outpath
diff --git a/python/l10n/mozxchannel/source.py b/python/l10n/mozxchannel/source.py
new file mode 100644
index 0000000000..b9d2067980
--- /dev/null
+++ b/python/l10n/mozxchannel/source.py
@@ -0,0 +1,88 @@
+# 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/.
+
+import toml
+from compare_locales import mozpath, paths
+from compare_locales.paths.matcher import expand
+
+from .projectconfig import generate_filename
+
+
+class HGFiles(paths.ProjectFiles):
+ def __init__(self, repo, rev, projects):
+ self.repo = repo
+ self.ctx = repo[rev]
+ self.root = repo.root()
+ self.manifest = None
+ self.configs_map = {}
+ # get paths for our TOML files
+ for p in projects:
+ all_configpaths = {
+ mozpath.abspath(c.path).encode("utf-8") for c in p.configs
+ }
+ for refpath in all_configpaths:
+ local_path = mozpath.relpath(refpath, self.root)
+ if local_path not in self.ctx:
+ print("ignoring", refpath)
+ continue
+ targetpath = b"/".join(
+ (
+ expand(None, "{l10n_base}", p.environ).encode("utf-8"),
+ b"en-US",
+ generate_filename(local_path),
+ )
+ )
+ self.configs_map[refpath] = targetpath
+ super(HGFiles, self).__init__("en-US", projects)
+ for m in self.matchers:
+ m["l10n"].encoding = "utf-8"
+ if "reference" in m:
+ m["reference"].encoding = "utf-8"
+ if self.exclude:
+ for m in self.exclude.matchers:
+ m["l10n"].encoding = "utf-8"
+ if "reference" in m:
+ m["reference"].encoding = "utf-8"
+
+ def _files(self, matcher):
+ for f in self.ctx.manifest():
+ f = mozpath.join(self.root, f)
+ if matcher.match(f):
+ yield f
+
+ def __iter__(self):
+ for t in super(HGFiles, self).__iter__():
+ yield t
+ for refpath, targetpath in self.configs_map.items():
+ yield targetpath, refpath, None, set()
+
+ def match(self, path):
+ m = super(HGFiles, self).match(path)
+ if m:
+ return m
+ for refpath, targetpath in self.configs_map.items():
+ if path in [refpath, targetpath]:
+ return targetpath, refpath, None, set()
+
+
+class HgTOMLParser(paths.TOMLParser):
+ "subclass to load from our hg context"
+
+ def __init__(self, repo, rev):
+ self.repo = repo
+ self.rev = rev
+ self.root = repo.root().decode("utf-8")
+
+ def load(self, parse_ctx):
+ try:
+ path = parse_ctx.path
+ local_path = "path:" + mozpath.relpath(path, self.root)
+ data = self.repo.cat(files=[local_path.encode("utf-8")], rev=self.rev)
+ except Exception:
+ raise paths.ConfigNotFound(parse_ctx.path)
+
+ try:
+ parse_ctx.data = toml.loads(data.decode())
+ except toml.TomlDecodeError as e:
+ raise RuntimeError(f"In file '{parse_ctx.path}':\n {e!s}") from e
diff --git a/python/l10n/test_fluent_migrations/__init__.py b/python/l10n/test_fluent_migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/python/l10n/test_fluent_migrations/__init__.py
diff --git a/python/l10n/test_fluent_migrations/fmt.py b/python/l10n/test_fluent_migrations/fmt.py
new file mode 100644
index 0000000000..198158fa5a
--- /dev/null
+++ b/python/l10n/test_fluent_migrations/fmt.py
@@ -0,0 +1,198 @@
+import codecs
+import logging
+import os
+import re
+import shutil
+import sys
+from datetime import datetime, timedelta
+from difflib import unified_diff
+from typing import Iterable
+
+import hglib
+from compare_locales.merge import merge_channels
+from compare_locales.paths.configparser import TOMLParser
+from compare_locales.paths.files import ProjectFiles
+from fluent.migrate.repo_client import RepoClient, git
+from fluent.migrate.validator import Validator
+from fluent.syntax import FluentParser, FluentSerializer
+from mach.util import get_state_dir
+from mozpack.path import join, normpath
+from mozversioncontrol.repoupdate import update_git_repo, update_mercurial_repo
+
+L10N_SOURCE_NAME = "l10n-source"
+L10N_SOURCE_REPO = "https://github.com/mozilla-l10n/firefox-l10n-source.git"
+
+STRINGS_NAME = "gecko-strings"
+STRINGS_REPO = "https://hg.mozilla.org/l10n/gecko-strings"
+
+PULL_AFTER = timedelta(days=2)
+
+
+def inspect_migration(path):
+ """Validate recipe and extract some metadata."""
+ return Validator.validate(path)
+
+
+def prepare_directories(cmd, use_git=False):
+ """
+ Ensure object dir exists,
+ and that repo dir has a relatively up-to-date clone of l10n-source or gecko-strings.
+
+ We run this once per mach invocation, for all tested migrations.
+ """
+ obj_dir = join(cmd.topobjdir, "python", "l10n")
+ if not os.path.exists(obj_dir):
+ os.makedirs(obj_dir)
+
+ if use_git:
+ repo_dir = join(get_state_dir(), L10N_SOURCE_NAME)
+ marker = join(repo_dir, ".git", "l10n_pull_marker")
+ else:
+ repo_dir = join(get_state_dir(), STRINGS_NAME)
+ marker = join(repo_dir, ".hg", "l10n_pull_marker")
+
+ try:
+ last_pull = datetime.fromtimestamp(os.stat(marker).st_mtime)
+ skip_clone = datetime.now() < last_pull + PULL_AFTER
+ except OSError:
+ skip_clone = False
+ if not skip_clone:
+ if use_git:
+ update_git_repo(L10N_SOURCE_REPO, repo_dir)
+ else:
+ update_mercurial_repo(STRINGS_REPO, repo_dir)
+ with open(marker, "w") as fh:
+ fh.flush()
+
+ return obj_dir, repo_dir
+
+
+def diff_resources(left_path, right_path):
+ parser = FluentParser(with_spans=False)
+ serializer = FluentSerializer(with_junk=True)
+ lines = []
+ for p in (left_path, right_path):
+ with codecs.open(p, encoding="utf-8") as fh:
+ res = parser.parse(fh.read())
+ lines.append(serializer.serialize(res).splitlines(True))
+ sys.stdout.writelines(
+ chunk for chunk in unified_diff(lines[0], lines[1], left_path, right_path)
+ )
+
+
+def test_migration(
+ cmd,
+ obj_dir: str,
+ repo_dir: str,
+ use_git: bool,
+ to_test: list[str],
+ references: Iterable[str],
+):
+ """Test the given recipe.
+
+ This creates a workdir by l10n-merging gecko-strings and the m-c source,
+ to mimic gecko-strings after the patch to test landed.
+ It then runs the recipe with a gecko-strings clone as localization, both
+ dry and wet.
+ It inspects the generated commits, and shows a diff between the merged
+ reference and the generated content.
+ The diff is intended to be visually inspected. Some changes might be
+ expected, in particular when formatting of the en-US strings is different.
+ """
+ rv = 0
+ migration_name = os.path.splitext(os.path.split(to_test)[1])[0]
+ work_dir = join(obj_dir, migration_name)
+
+ paths = os.path.normpath(to_test).split(os.sep)
+ # Migration modules should be in a sub-folder of l10n.
+ migration_module = (
+ ".".join(paths[paths.index("l10n") + 1 : -1]) + "." + migration_name
+ )
+
+ if os.path.exists(work_dir):
+ shutil.rmtree(work_dir)
+ os.makedirs(join(work_dir, "reference"))
+ l10n_toml = join(cmd.topsrcdir, cmd.substs["MOZ_BUILD_APP"], "locales", "l10n.toml")
+ pc = TOMLParser().parse(l10n_toml, env={"l10n_base": work_dir})
+ pc.set_locales(["reference"])
+ files = ProjectFiles("reference", [pc])
+ ref_root = join(work_dir, "reference")
+ for ref in references:
+ if ref != normpath(ref):
+ cmd.log(
+ logging.ERROR,
+ "fluent-migration-test",
+ {"file": to_test, "ref": ref},
+ 'Reference path "{ref}" needs to be normalized for {file}',
+ )
+ rv = 1
+ continue
+ full_ref = join(ref_root, ref)
+ m = files.match(full_ref)
+ if m is None:
+ raise ValueError("Bad reference path: " + ref)
+ m_c_path = m[1]
+ g_s_path = join(work_dir, L10N_SOURCE_NAME if use_git else STRINGS_NAME, ref)
+ resources = [
+ b"" if not os.path.exists(f) else open(f, "rb").read()
+ for f in (g_s_path, m_c_path)
+ ]
+ ref_dir = os.path.dirname(full_ref)
+ if not os.path.exists(ref_dir):
+ os.makedirs(ref_dir)
+ open(full_ref, "wb").write(merge_channels(ref, resources))
+ l10n_root = join(work_dir, "en-US")
+ if use_git:
+ git(work_dir, "clone", repo_dir, l10n_root)
+ else:
+ hglib.clone(source=repo_dir, dest=l10n_root)
+ client = RepoClient(l10n_root)
+ old_tip = client.head()
+ run_migration = [
+ cmd._virtualenv_manager.python_path,
+ "-m",
+ "fluent.migrate.tool",
+ "--lang",
+ "en-US",
+ "--reference-dir",
+ ref_root,
+ "--localization-dir",
+ l10n_root,
+ "--dry-run",
+ migration_module,
+ ]
+ cmd.run_process(run_migration, cwd=work_dir, line_handler=print)
+ # drop --dry-run
+ run_migration.pop(-2)
+ cmd.run_process(run_migration, cwd=work_dir, line_handler=print)
+ tip = client.head()
+ if old_tip == tip:
+ cmd.log(
+ logging.WARN,
+ "fluent-migration-test",
+ {"file": to_test},
+ "No migration applied for {file}",
+ )
+ return rv
+ for ref in references:
+ diff_resources(join(ref_root, ref), join(l10n_root, ref))
+ messages = client.log(old_tip, tip)
+ bug = re.search("[0-9]{5,}", migration_name)
+ # Just check first message for bug number, they're all following the same pattern
+ if bug is None or bug.group() not in messages[0]:
+ rv = 1
+ cmd.log(
+ logging.ERROR,
+ "fluent-migration-test",
+ {"file": to_test},
+ "Missing or wrong bug number for {file}",
+ )
+ if any("part {}".format(n + 1) not in msg for n, msg in enumerate(messages)):
+ rv = 1
+ cmd.log(
+ logging.ERROR,
+ "fluent-migration-test",
+ {"file": to_test},
+ 'Commit messages should have "part {{index}}" for {file}',
+ )
+ return rv