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_1738056_aboutDialog_channel.py33
-rw-r--r--python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py65
-rw-r--r--python/l10n/fluent_migrations/bug_1793557_extensions.py912
-rw-r--r--python/l10n/fluent_migrations/bug_1793572_webrtc.py771
-rw-r--r--python/l10n/fluent_migrations/bug_1813077_popup_notification_learn_more.py22
-rw-r--r--python/l10n/fluent_migrations/bug_1814261_mixed_content_identity_panel.py49
-rw-r--r--python/l10n/fluent_migrations/bug_1814266_identity_custom_root.py34
-rw-r--r--python/l10n/fluent_migrations/bug_1818322_mozTabList.py26
-rw-r--r--python/l10n/fluent_migrations/bug_1820654_update_manual.py30
-rw-r--r--python/l10n/fluent_migrations/bug_1821187_migrationWizard_password_file_import_strings.py27
-rw-r--r--python/l10n/fluent_migrations/bug_1821779_migrationWizard_browser_names.py38
-rw-r--r--python/l10n/fluent_migrations/bug_1828443_pocket_policy.py30
-rw-r--r--python/l10n/fluent_migrations/bug_1828767_sanitize_dialog_native_size.py77
-rw-r--r--python/l10n/fluent_migrations/bug_1830042_places.py127
-rw-r--r--python/l10n/fluent_migrations/bug_1831851_accounts.py195
-rw-r--r--python/l10n/fluent_migrations/bug_1831872_sync.py31
-rw-r--r--python/l10n/fluent_migrations/bug_1832138_ctrlTab.py37
-rw-r--r--python/l10n/fluent_migrations/bug_1832141_recently_closed.py76
-rw-r--r--python/l10n/fluent_migrations/bug_1832179_sendTabToDevice.py37
-rw-r--r--python/l10n/fluent_migrations/bug_1832186_popupwarning.py139
-rw-r--r--python/l10n/fluent_migrations/bug_1832668_firefoxView_navigation.py27
-rw-r--r--python/l10n/fluent_migrations/bug_1833228_fxviewTabList.py44
-rw-r--r--python/l10n/fluent_migrations/bug_1835559_aboutDialog_explicit_textContent.py67
-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/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.py188
33 files changed, 3563 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_1738056_aboutDialog_channel.py b/python/l10n/fluent_migrations/bug_1738056_aboutDialog_channel.py
new file mode 100644
index 0000000000..b867155ebf
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1738056_aboutDialog_channel.py
@@ -0,0 +1,33 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+import re
+
+from fluent.migrate.transforms import TransformPattern
+
+
+class INSERT_VARIABLE(TransformPattern):
+ def visit_TextElement(self, node):
+ node.value = re.sub(
+ 'current-channel"></label',
+ 'current-channel">{ $channel }</label',
+ node.value,
+ )
+ return node
+
+
+def migrate(ctx):
+ """Bug 1738056 - Convert about dialog channel listing to fluent, part {index}."""
+
+ about_dialog_ftl = "browser/browser/aboutDialog.ftl"
+ ctx.add_transforms(
+ about_dialog_ftl,
+ about_dialog_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("aboutdialog-channel-description"),
+ value=INSERT_VARIABLE(about_dialog_ftl, "channel-description"),
+ ),
+ ],
+ )
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_1793557_extensions.py b/python/l10n/fluent_migrations/bug_1793557_extensions.py
new file mode 100644
index 0000000000..0c04a87509
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1793557_extensions.py
@@ -0,0 +1,912 @@
+# 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 1793557 - Convert extension strings to Fluent, part {index}."""
+
+ browser_properties = "browser/chrome/browser/browser.properties"
+ browser_ftl = "browser/browser/browser.ftl"
+ notifications = "browser/browser/addonNotifications.ftl"
+ extensions_ui = "browser/browser/extensionsUI.ftl"
+ extensions = "toolkit/toolkit/global/extensions.ftl"
+ permissions = "toolkit/toolkit/global/extensionPermissions.ftl"
+
+ ctx.add_transforms(
+ browser_ftl,
+ browser_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("popup-notification-addon-install-unsigned"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("value"),
+ value=COPY(browser_properties, "addonInstall.unsigned"),
+ )
+ ],
+ ),
+ ],
+ )
+
+ ctx.add_transforms(
+ notifications,
+ notifications,
+ [
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt"),
+ value=REPLACE(
+ browser_properties,
+ "xpinstallPromptMessage",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-header"),
+ value=REPLACE(
+ browser_properties,
+ "xpinstallPromptMessage.header",
+ {"%1$S": VARIABLE_REFERENCE("host")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-message"),
+ value=REPLACE(
+ browser_properties,
+ "xpinstallPromptMessage.message",
+ {"%1$S": VARIABLE_REFERENCE("host")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-header-unknown"),
+ value=COPY(browser_properties, "xpinstallPromptMessage.header.unknown"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-message-unknown"),
+ value=COPY(
+ browser_properties, "xpinstallPromptMessage.message.unknown"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-dont-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "xpinstallPromptMessage.dontAllow"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties,
+ "xpinstallPromptMessage.dontAllow.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-never-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "xpinstallPromptMessage.neverAllow"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties,
+ "xpinstallPromptMessage.neverAllow.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-never-allow-and-report"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties,
+ "xpinstallPromptMessage.neverAllowAndReport",
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties,
+ "xpinstallPromptMessage.neverAllowAndReport.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("site-permission-install-first-prompt-midi-header"),
+ value=COPY(
+ browser_properties, "sitePermissionInstallFirstPrompt.midi.header"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("site-permission-install-first-prompt-midi-message"),
+ value=COPY(
+ browser_properties, "sitePermissionInstallFirstPrompt.midi.message"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-install"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "xpinstallPromptMessage.install"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties,
+ "xpinstallPromptMessage.install.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-disabled-locked"),
+ value=COPY(browser_properties, "xpinstallDisabledMessageLocked"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-disabled"),
+ value=COPY(browser_properties, "xpinstallDisabledMessage"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-disabled-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser_properties, "xpinstallDisabledButton"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "xpinstallDisabledButton.accesskey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-blocked-by-policy"),
+ value=REPLACE(
+ browser_properties,
+ "addonInstallBlockedByPolicy",
+ {
+ "%1$S": VARIABLE_REFERENCE("addonName"),
+ "%2$S": VARIABLE_REFERENCE("addonId"),
+ "%3$S": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-domain-blocked-by-policy"),
+ value=COPY(browser_properties, "addonDomainBlockedByPolicy"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-full-screen-blocked"),
+ value=COPY(browser_properties, "addonInstallFullScreenBlocked"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-sideload-menu-item"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.sideloadMenuItem",
+ {
+ "%1$S": VARIABLE_REFERENCE("addonName"),
+ "%2$S": TERM_REFERENCE("brand-short-name"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-update-menu-item"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.updateMenuItem",
+ {"%1$S": VARIABLE_REFERENCE("addonName")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-removal-title"),
+ value=COPY_PATTERN(browser_ftl, "addon-removal-title"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-removal-message"),
+ value=REPLACE(
+ browser_properties,
+ "webext.remove.confirmation.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("name"),
+ "%2$S": TERM_REFERENCE("brand-shorter-name"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-removal-button"),
+ value=COPY(browser_properties, "webext.remove.confirmation.button"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-removal-abuse-report-checkbox"),
+ value=COPY_PATTERN(browser_ftl, "addon-removal-abuse-report-checkbox"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-downloading-and-verifying"),
+ value=PLURALS(
+ browser_properties,
+ "addonDownloadingAndVerifying",
+ VARIABLE_REFERENCE("addonCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("addonCount")},
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-download-verifying"),
+ value=COPY(browser_properties, "addonDownloadVerifying"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-cancel-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "addonInstall.cancelButton.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "addonInstall.cancelButton.accesskey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-accept-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "addonInstall.acceptButton2.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "addonInstall.acceptButton2.accesskey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-confirm-install-message"),
+ value=PLURALS(
+ browser_properties,
+ "addonConfirmInstall.message",
+ VARIABLE_REFERENCE("addonCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": TERM_REFERENCE("brand-short-name"),
+ "#2": VARIABLE_REFERENCE("addonCount"),
+ },
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-confirm-install-unsigned-message"),
+ value=PLURALS(
+ browser_properties,
+ "addonConfirmInstallUnsigned.message",
+ VARIABLE_REFERENCE("addonCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": TERM_REFERENCE("brand-short-name"),
+ "#2": VARIABLE_REFERENCE("addonCount"),
+ },
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-confirm-install-some-unsigned-message"),
+ value=PLURALS(
+ browser_properties,
+ "addonConfirmInstallSomeUnsigned.message",
+ VARIABLE_REFERENCE("addonCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": TERM_REFERENCE("brand-short-name"),
+ "#2": VARIABLE_REFERENCE("addonCount"),
+ },
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-network-failure"),
+ value=COPY(browser_properties, "addonInstallError-1"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-incorrect-hash"),
+ value=REPLACE(
+ browser_properties,
+ "addonInstallError-2",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-corrupt-file"),
+ value=COPY(browser_properties, "addonInstallError-3"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-file-access"),
+ value=REPLACE(
+ browser_properties,
+ "addonInstallError-4",
+ {
+ "%2$S": VARIABLE_REFERENCE("addonName"),
+ "%1$S": TERM_REFERENCE("brand-short-name"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-not-signed"),
+ value=REPLACE(
+ browser_properties,
+ "addonInstallError-5",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-invalid-domain"),
+ value=REPLACE(
+ browser_properties,
+ "addonInstallError-8",
+ {"%2$S": VARIABLE_REFERENCE("addonName")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-network-failure"),
+ value=COPY(browser_properties, "addonLocalInstallError-1"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-incorrect-hash"),
+ value=REPLACE(
+ browser_properties,
+ "addonLocalInstallError-2",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-corrupt-file"),
+ value=COPY(browser_properties, "addonLocalInstallError-3"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-file-access"),
+ value=REPLACE(
+ browser_properties,
+ "addonLocalInstallError-4",
+ {
+ "%2$S": VARIABLE_REFERENCE("addonName"),
+ "%1$S": TERM_REFERENCE("brand-short-name"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-not-signed"),
+ value=COPY(browser_properties, "addonLocalInstallError-5"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-incompatible"),
+ value=REPLACE(
+ browser_properties,
+ "addonInstallErrorIncompatible",
+ {
+ "%3$S": VARIABLE_REFERENCE("addonName"),
+ "%1$S": TERM_REFERENCE("brand-short-name"),
+ "%2$S": VARIABLE_REFERENCE("appVersion"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-blocklisted"),
+ value=REPLACE(
+ browser_properties,
+ "addonInstallErrorBlocklisted",
+ {"%1$S": VARIABLE_REFERENCE("addonName")},
+ ),
+ ),
+ ],
+ )
+
+ ctx.add_transforms(
+ extensions_ui,
+ extensions_ui,
+ [
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-learn-more"),
+ value=COPY(browser_properties, "webextPerms.learnMore2"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-default-search-description"),
+ value=REPLACE(
+ browser_properties,
+ "webext.defaultSearch.description",
+ {
+ "%1$S": VARIABLE_REFERENCE("addonName"),
+ "%2$S": VARIABLE_REFERENCE("currentEngine"),
+ "%3$S": VARIABLE_REFERENCE("newEngine"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-default-search-yes"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser_properties, "webext.defaultSearchYes.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "webext.defaultSearchYes.accessKey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-default-search-no"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser_properties, "webext.defaultSearchNo.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "webext.defaultSearchNo.accessKey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-post-install-message"),
+ value=REPLACE(
+ browser_properties,
+ "addonPostInstall.message3",
+ {"%1$S": VARIABLE_REFERENCE("addonName")},
+ ),
+ ),
+ ],
+ )
+
+ ctx.add_transforms(
+ extensions,
+ extensions,
+ [
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-header"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.header",
+ {"%1$S": VARIABLE_REFERENCE("extension")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-header-with-perms"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.headerWithPerms",
+ {"%1$S": VARIABLE_REFERENCE("extension")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-header-unsigned"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.headerUnsigned",
+ {"%1$S": VARIABLE_REFERENCE("extension")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-header-unsigned-with-perms"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.headerUnsignedWithPerms",
+ {"%1$S": VARIABLE_REFERENCE("extension")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-add"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser_properties, "webextPerms.add.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(browser_properties, "webextPerms.add.accessKey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-cancel"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(browser_properties, "webextPerms.cancel.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(browser_properties, "webextPerms.cancel.accessKey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-sideload-header"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.sideloadHeader",
+ {"%1$S": VARIABLE_REFERENCE("extension")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-sideload-text"),
+ value=COPY(browser_properties, "webextPerms.sideloadText2"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-sideload-text-no-perms"),
+ value=COPY(browser_properties, "webextPerms.sideloadTextNoPerms"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-sideload-enable"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "webextPerms.sideloadEnable.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "webextPerms.sideloadEnable.accessKey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-sideload-cancel"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "webextPerms.sideloadCancel.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "webextPerms.sideloadCancel.accessKey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-update-text"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.updateText2",
+ {"%1$S": VARIABLE_REFERENCE("extension")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-update-accept"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "webextPerms.updateAccept.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties, "webextPerms.updateAccept.accessKey"
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-optional-perms-header"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.optionalPermsHeader",
+ {"%1$S": VARIABLE_REFERENCE("extension")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-optional-perms-list-intro"),
+ value=COPY(browser_properties, "webextPerms.optionalPermsListIntro"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-optional-perms-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "webextPerms.optionalPermsAllow.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties,
+ "webextPerms.optionalPermsAllow.accessKey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-optional-perms-deny"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ browser_properties, "webextPerms.optionalPermsDeny.label"
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ browser_properties,
+ "webextPerms.optionalPermsDeny.accessKey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-host-description-all-urls"),
+ value=COPY(browser_properties, "webextPerms.hostDescription.allUrls"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-host-description-wildcard"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.hostDescription.wildcard",
+ {"%1$S": VARIABLE_REFERENCE("domain")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-host-description-too-many-wildcards"),
+ value=PLURALS(
+ browser_properties,
+ "webextPerms.hostDescription.tooManyWildcards",
+ VARIABLE_REFERENCE("domainCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("domainCount")},
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-host-description-one-site"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.hostDescription.oneSite",
+ {"%1$S": VARIABLE_REFERENCE("domain")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-host-description-too-many-sites"),
+ value=PLURALS(
+ browser_properties,
+ "webextPerms.hostDescription.tooManySites",
+ VARIABLE_REFERENCE("domainCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("domainCount")},
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-site-perms-header-with-gated-perms-midi"),
+ value=REPLACE(
+ browser_properties,
+ "webextSitePerms.headerWithGatedPerms.midi",
+ {
+ "%1$S": VARIABLE_REFERENCE("hostname"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webext-site-perms-header-with-gated-perms-midi-sysex"
+ ),
+ value=REPLACE(
+ browser_properties,
+ "webextSitePerms.headerWithGatedPerms.midi-sysex",
+ {
+ "%1$S": VARIABLE_REFERENCE("hostname"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-site-perms-description-gated-perms-midi"),
+ value=COPY(
+ browser_properties, "webextSitePerms.descriptionGatedPerms.midi"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-site-perms-header-with-perms"),
+ value=REPLACE(
+ browser_properties,
+ "webextSitePerms.headerWithPerms",
+ {
+ "%1$S": VARIABLE_REFERENCE("extension"),
+ "%2$S": VARIABLE_REFERENCE("hostname"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-site-perms-header-unsigned-with-perms"),
+ value=REPLACE(
+ browser_properties,
+ "webextSitePerms.headerUnsignedWithPerms",
+ {
+ "%1$S": VARIABLE_REFERENCE("extension"),
+ "%2$S": VARIABLE_REFERENCE("hostname"),
+ },
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-site-perms-midi"),
+ value=COPY(browser_properties, "webextSitePerms.description.midi"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-site-perms-midi-sysex"),
+ value=COPY(
+ browser_properties, "webextSitePerms.description.midi-sysex"
+ ),
+ ),
+ ],
+ )
+
+ ctx.add_transforms(
+ permissions,
+ permissions,
+ [
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-bookmarks"),
+ value=COPY(browser_properties, "webextPerms.description.bookmarks"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-browserSettings"),
+ value=COPY(
+ browser_properties, "webextPerms.description.browserSettings"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-browsingData"),
+ value=COPY(browser_properties, "webextPerms.description.browsingData"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-clipboardRead"),
+ value=COPY(browser_properties, "webextPerms.description.clipboardRead"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-clipboardWrite"),
+ value=COPY(
+ browser_properties, "webextPerms.description.clipboardWrite"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-declarativeNetRequest"),
+ value=COPY(
+ browser_properties, "webextPerms.description.declarativeNetRequest"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "webext-perms-description-declarativeNetRequestFeedback"
+ ),
+ value=COPY(
+ browser_properties,
+ "webextPerms.description.declarativeNetRequestFeedback",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-devtools"),
+ value=COPY(browser_properties, "webextPerms.description.devtools"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-downloads"),
+ value=COPY(browser_properties, "webextPerms.description.downloads"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-downloads-open"),
+ value=COPY(
+ browser_properties, "webextPerms.description.downloads.open"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-find"),
+ value=COPY(browser_properties, "webextPerms.description.find"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-geolocation"),
+ value=COPY(browser_properties, "webextPerms.description.geolocation"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-history"),
+ value=COPY(browser_properties, "webextPerms.description.history"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-management"),
+ value=COPY(browser_properties, "webextPerms.description.management"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-nativeMessaging"),
+ value=REPLACE(
+ browser_properties,
+ "webextPerms.description.nativeMessaging",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-notifications"),
+ value=COPY(browser_properties, "webextPerms.description.notifications"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-pkcs11"),
+ value=COPY(browser_properties, "webextPerms.description.pkcs11"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-privacy"),
+ value=COPY(browser_properties, "webextPerms.description.privacy"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-proxy"),
+ value=COPY(browser_properties, "webextPerms.description.proxy"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-sessions"),
+ value=COPY(browser_properties, "webextPerms.description.sessions"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-tabs"),
+ value=COPY(browser_properties, "webextPerms.description.tabs"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-tabHide"),
+ value=COPY(browser_properties, "webextPerms.description.tabHide"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-topSites"),
+ value=COPY(browser_properties, "webextPerms.description.topSites"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-webNavigation"),
+ value=COPY(browser_properties, "webextPerms.description.webNavigation"),
+ ),
+ ],
+ )
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_1813077_popup_notification_learn_more.py b/python/l10n/fluent_migrations/bug_1813077_popup_notification_learn_more.py
new file mode 100644
index 0000000000..bba68d163e
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1813077_popup_notification_learn_more.py
@@ -0,0 +1,22 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1813077 - Migrate xpinstallPromptMessage.learnMore to Fluent , part {index}."""
+
+ ctx.add_transforms(
+ "browser/browser/browser.ftl",
+ "browser/browser/browser.ftl",
+ transforms_from(
+ """
+popup-notification-xpinstall-prompt-learn-more = { COPY("browser/chrome/browser/browser.properties", "xpinstallPromptMessage.learnMore") }
+"""
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1814261_mixed_content_identity_panel.py b/python/l10n/fluent_migrations/bug_1814261_mixed_content_identity_panel.py
new file mode 100644
index 0000000000..a135870df8
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1814261_mixed_content_identity_panel.py
@@ -0,0 +1,49 @@
+# 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_LEARNMORE(TransformPattern):
+ # Used to remove `<a data-l10n-name="link">SOME TEXT</a>` from a string
+ def visit_TextElement(self, node):
+ link_start = node.value.find('<label data-l10n-name="link">')
+ if link_start != -1:
+ # Replace string up to the link, remove remaining spaces afterwards.
+ # Removing an extra character directly is not safe, as it could be
+ # punctuation.
+ node.value = node.value[:link_start].rstrip()
+
+ return node
+
+
+def migrate(ctx):
+ """Bug 1814261 - Use moz-support-link in the mixed-content section of the identity panel, part {index}."""
+
+ browser_ftl = "browser/browser/browser.ftl"
+ ctx.add_transforms(
+ browser_ftl,
+ browser_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("identity-description-active-blocked2"),
+ value=STRIP_LEARNMORE(
+ browser_ftl, "identity-description-active-blocked"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("identity-description-passive-loaded-insecure2"),
+ value=STRIP_LEARNMORE(
+ browser_ftl, "identity-description-passive-loaded-insecure"
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("identity-description-passive-loaded-mixed2"),
+ value=STRIP_LEARNMORE(
+ browser_ftl, "identity-description-passive-loaded-mixed"
+ ),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1814266_identity_custom_root.py b/python/l10n/fluent_migrations/bug_1814266_identity_custom_root.py
new file mode 100644
index 0000000000..7eb144230d
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1814266_identity_custom_root.py
@@ -0,0 +1,34 @@
+# 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_LEARNMORE(TransformPattern):
+ # Used to remove `<a data-l10n-name="link">SOME TEXT</a>` from a string
+ def visit_TextElement(self, node):
+ link_start = node.value.find('<label data-l10n-name="link">')
+ # Replace string up to the link, remove remaining spaces afterwards.
+ # Removing an extra character directly is not safe, as it could be
+ # punctuation.
+ node.value = node.value[:link_start].rstrip()
+
+ return node
+
+
+def migrate(ctx):
+ """Bug 1814266 - Use moz-support-link in identity panel, part {index}."""
+
+ browser_ftl = "browser/browser/browser.ftl"
+ ctx.add_transforms(
+ browser_ftl,
+ browser_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("identity-description-custom-root2"),
+ value=STRIP_LEARNMORE(browser_ftl, "identity-description-custom-root"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1818322_mozTabList.py b/python/l10n/fluent_migrations/bug_1818322_mozTabList.py
new file mode 100644
index 0000000000..9be3037550
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1818322_mozTabList.py
@@ -0,0 +1,26 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate import COPY
+
+
+def migrate(ctx):
+ """Bug 1818322 - Create MozTabList and MozTabRow reusable components, part {index}."""
+ ctx.add_transforms(
+ "toolkit/toolkit/global/mozTabList.ftl",
+ "toolkit/toolkit/global/mozTabList.ftl",
+ transforms_from(
+ """
+mztabrow-tabs-list-tab =
+ .title = {COPY_PATTERN(from_path, "firefoxview-tabs-list-tab-button.title")}
+mztabrow-dismiss-tab-button =
+ .title = {COPY_PATTERN(from_path, "firefoxview-closed-tabs-dismiss-tab.title")}
+mztabrow-just-now-timestamp = {COPY_PATTERN(from_path, "firefoxview-just-now-timestamp")}
+ """,
+ from_path="browser/browser/firefoxView.ftl",
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1820654_update_manual.py b/python/l10n/fluent_migrations/bug_1820654_update_manual.py
new file mode 100644
index 0000000000..fe3a18e38b
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1820654_update_manual.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.transforms import TransformPattern
+
+
+class REPLACE_LABEL(TransformPattern):
+ # Used to replace `<label data-l10n-name="manual-link"/>`
+ def visit_TextElement(self, node):
+ node.value = node.value.replace("<label", "<a")
+
+ return node
+
+
+def migrate(ctx):
+ """Bug 1820654 - Use html:a in manualUpdate box, part {index}."""
+
+ aboutDialog_ftl = "browser/browser/aboutDialog.ftl"
+ ctx.add_transforms(
+ aboutDialog_ftl,
+ aboutDialog_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("aboutdialog-update-manual"),
+ value=REPLACE_LABEL(aboutDialog_ftl, "update-manual"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1821187_migrationWizard_password_file_import_strings.py b/python/l10n/fluent_migrations/bug_1821187_migrationWizard_password_file_import_strings.py
new file mode 100644
index 0000000000..9f74ff9837
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1821187_migrationWizard_password_file_import_strings.py
@@ -0,0 +1,27 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1821187 - Copy password file import strings to migrationWizard.ftl, part {index}."""
+
+ ctx.add_transforms(
+ "browser/browser/migrationWizard.ftl",
+ "browser/browser/migrationWizard.ftl",
+ transforms_from(
+ """
+migration-passwords-from-file-csv-filter-title =
+ {COPY_PATTERN(from_path, "about-logins-import-file-picker-csv-filter-title")}
+migration-passwords-from-file-tsv-filter-title =
+ {COPY_PATTERN(from_path, "about-logins-import-file-picker-tsv-filter-title")}
+ """,
+ from_path="browser/browser/aboutLogins.ftl",
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1821779_migrationWizard_browser_names.py b/python/l10n/fluent_migrations/bug_1821779_migrationWizard_browser_names.py
new file mode 100644
index 0000000000..6bcaa35da8
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1821779_migrationWizard_browser_names.py
@@ -0,0 +1,38 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1821779 - Move existing browser names to migrationWizard.ftl, part {index}."""
+
+ ctx.add_transforms(
+ "browser/browser/migrationWizard.ftl",
+ "browser/browser/migrationWizard.ftl",
+ transforms_from(
+ """
+migration-wizard-migrator-display-name-brave = {COPY_PATTERN(from_path, "import-from-brave.label")}
+migration-wizard-migrator-display-name-canary = {COPY_PATTERN(from_path, "import-from-canary.label")}
+migration-wizard-migrator-display-name-chrome = {COPY_PATTERN(from_path, "import-from-chrome.label")}
+migration-wizard-migrator-display-name-chrome-beta = {COPY_PATTERN(from_path, "import-from-chrome-beta.label")}
+migration-wizard-migrator-display-name-chrome-dev = {COPY_PATTERN(from_path, "import-from-chrome-dev.label")}
+migration-wizard-migrator-display-name-chromium = {COPY_PATTERN(from_path, "import-from-chromium.label")}
+migration-wizard-migrator-display-name-chromium-360se = {COPY_PATTERN(from_path, "import-from-360se.label")}
+migration-wizard-migrator-display-name-chromium-edge = {COPY_PATTERN(from_path, "import-from-edge.label")}
+migration-wizard-migrator-display-name-chromium-edge-beta = {COPY_PATTERN(from_path, "import-from-edge-beta.label")}
+migration-wizard-migrator-display-name-edge-legacy = {COPY_PATTERN(from_path, "import-from-edge-legacy.label")}
+migration-wizard-migrator-display-name-firefox = {COPY_PATTERN(from_path, "import-from-firefox.label")}
+migration-wizard-migrator-display-name-ie = {COPY_PATTERN(from_path, "import-from-ie.label")}
+migration-wizard-migrator-display-name-opera = {COPY_PATTERN(from_path, "import-from-opera.label")}
+migration-wizard-migrator-display-name-opera-gx = {COPY_PATTERN(from_path, "import-from-opera-gx.label")}
+migration-wizard-migrator-display-name-safari = {COPY_PATTERN(from_path, "import-from-safari.label")}
+migration-wizard-migrator-display-name-vivaldi = {COPY_PATTERN(from_path, "import-from-vivaldi.label")}
+""",
+ from_path="browser/browser/migration.ftl",
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1828443_pocket_policy.py b/python/l10n/fluent_migrations/bug_1828443_pocket_policy.py
new file mode 100644
index 0000000000..6f9fbe5d7b
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1828443_pocket_policy.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.transforms import TransformPattern
+
+
+class REPLACE_POCKET(TransformPattern):
+ # Replace Pocket with term `{ -pocket-brand-name }`
+ def visit_TextElement(self, node):
+ node.value = node.value.replace("Pocket", "{ -pocket-brand-name }")
+
+ return node
+
+
+def migrate(ctx):
+ """Bug 1828443 - Use Fluent term for Pocket in policy string, part {index}."""
+
+ ftl_file = "browser/browser/policies/policies-descriptions.ftl"
+ ctx.add_transforms(
+ ftl_file,
+ ftl_file,
+ [
+ FTL.Message(
+ id=FTL.Identifier("policy-DisablePocket2"),
+ value=REPLACE_POCKET(ftl_file, "policy-DisablePocket"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1828767_sanitize_dialog_native_size.py b/python/l10n/fluent_migrations/bug_1828767_sanitize_dialog_native_size.py
new file mode 100644
index 0000000000..9d311c3fae
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1828767_sanitize_dialog_native_size.py
@@ -0,0 +1,77 @@
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.transforms import TransformPattern, REPLACE_IN_TEXT
+from fluent.migrate.helpers import MESSAGE_REFERENCE
+import fluent.syntax.ast as FTL
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+class REPLACE_PATTERN(TransformPattern):
+ """Hacky custom transform that works."""
+
+ def __init__(self, ctx, path, key, replacements, **kwargs):
+ super(REPLACE_PATTERN, self).__init__(path, key, **kwargs)
+ self.ctx = ctx
+ self.replacements = replacements
+
+ def visit_Pattern(self, source):
+ source = self.generic_visit(source)
+ target = FTL.Pattern([])
+ for element in source.elements:
+ if isinstance(element, FTL.TextElement):
+ pattern = REPLACE_IN_TEXT(element, self.replacements)(self.ctx)
+ target.elements += pattern.elements
+ else:
+ target.elements += [element]
+ return target
+
+
+def replace_with_min_size_transform(ctx, file, identifier, to_identifier=None):
+ if to_identifier is None:
+ to_identifier = identifier + "2"
+ attributes = [
+ FTL.Attribute(
+ id=FTL.Identifier("title"),
+ value=COPY_PATTERN(file, "{}.title".format(identifier)),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("style"),
+ value=REPLACE_PATTERN(
+ ctx,
+ file,
+ "{}.style".format(identifier),
+ {
+ "width:": FTL.TextElement("min-width:"),
+ "height:": FTL.TextElement("min-height:"),
+ },
+ ),
+ ),
+ ]
+
+ return FTL.Message(
+ id=FTL.Identifier(to_identifier),
+ attributes=attributes,
+ )
+
+
+def migrate(ctx):
+ """Bug 1828767 - Migrate sanitize dialog to use min-width, part {index}."""
+ ctx.add_transforms(
+ "browser/browser/sanitize.ftl",
+ "browser/browser/sanitize.ftl",
+ [
+ replace_with_min_size_transform(
+ ctx,
+ "browser/browser/sanitize.ftl",
+ "dialog-title",
+ "sanitize-dialog-title",
+ ),
+ replace_with_min_size_transform(
+ ctx,
+ "browser/browser/sanitize.ftl",
+ "dialog-title-everything",
+ "sanitize-dialog-title-everything",
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1830042_places.py b/python/l10n/fluent_migrations/bug_1830042_places.py
new file mode 100644
index 0000000000..0109d3952f
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1830042_places.py
@@ -0,0 +1,127 @@
+# 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, transforms_from, VARIABLE_REFERENCE
+from fluent.migrate.transforms import COPY, PLURALS, REPLACE, REPLACE_IN_TEXT
+
+
+def migrate(ctx):
+ """Bug 1830042 - Convert places.properties to Fluent, part {index}."""
+
+ source = "browser/chrome/browser/places/places.properties"
+ target = "browser/browser/places.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("places-details-pane-no-items"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("value"),
+ value=COPY(source, "detailsPane.noItems"),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("places-details-pane-items-count"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("value"),
+ value=PLURALS(
+ source,
+ "detailsPane.itemsCountLabel",
+ VARIABLE_REFERENCE("count"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("count")},
+ ),
+ ),
+ )
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("places-locked-prompt"),
+ value=REPLACE(
+ source,
+ "lockPrompt.text",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ ]
+ + transforms_from(
+ """
+places-empty-bookmarks-folder =
+ .label = { COPY(source, "bookmarksMenuEmptyFolder") }
+places-delete-page =
+ .label =
+ { $count ->
+ [1] { COPY(source, "cmd.deleteSinglePage.label") }
+ *[other] { COPY(source, "cmd.deleteMultiplePages.label") }
+ }
+ .accesskey = { COPY(source, "cmd.deleteSinglePage.accesskey") }
+places-create-bookmark =
+ .label =
+ { $count ->
+ [1] { COPY(source, "cmd.bookmarkSinglePage2.label") }
+ *[other] { COPY(source, "cmd.bookmarkMultiplePages2.label") }
+ }
+ .accesskey = { COPY(source, "cmd.bookmarkSinglePage2.accesskey") }
+places-search-bookmarks =
+ .placeholder = { COPY(source, "searchBookmarks") }
+places-search-history =
+ .placeholder = { COPY(source, "searchHistory") }
+places-search-downloads =
+ .placeholder = { COPY(source, "searchDownloads") }
+places-view-sortby-name =
+ .label = { COPY(source, "view.sortBy.1.name.label") }
+ .accesskey = { COPY(source, "view.sortBy.1.name.accesskey") }
+places-view-sortby-url =
+ .label = { COPY(source, "view.sortBy.1.url.label") }
+ .accesskey = { COPY(source, "view.sortBy.1.url.accesskey") }
+places-view-sortby-date =
+ .label = { COPY(source, "view.sortBy.1.date.label") }
+ .accesskey = { COPY(source, "view.sortBy.1.date.accesskey") }
+places-view-sortby-visit-count =
+ .label = { COPY(source, "view.sortBy.1.visitCount.label") }
+ .accesskey = { COPY(source, "view.sortBy.1.visitCount.accesskey") }
+places-view-sortby-date-added =
+ .label = { COPY(source, "view.sortBy.1.dateAdded.label") }
+ .accesskey = { COPY(source, "view.sortBy.1.dateAdded.accesskey") }
+places-view-sortby-last-modified =
+ .label = { COPY(source, "view.sortBy.1.lastModified.label") }
+ .accesskey = { COPY(source, "view.sortBy.1.lastModified.accesskey") }
+places-view-sortby-tags =
+ .label = { COPY(source, "view.sortBy.1.tags.label") }
+ .accesskey = { COPY(source, "view.sortBy.1.tags.accesskey") }
+""",
+ source=source,
+ ),
+ )
+
+ ctx.add_transforms(
+ "browser/browser/placesPrompts.ftl",
+ "browser/browser/placesPrompts.ftl",
+ [
+ FTL.Message(
+ id=FTL.Identifier("places-error-title"),
+ value=FTL.Pattern([FTL.Placeable(TERM_REFERENCE("brand-short-name"))]),
+ ),
+ ]
+ + transforms_from(
+ """
+places-no-title = { COPY(source, "noTitle") }
+places-bookmarks-backup-title = { COPY(source, "bookmarksBackupTitle") }
+places-bookmarks-restore-alert-title = { COPY(source, "bookmarksRestoreAlertTitle") }
+places-bookmarks-restore-alert = { COPY(source, "bookmarksRestoreAlert") }
+places-bookmarks-restore-title = { COPY(source, "bookmarksRestoreTitle") }
+places-bookmarks-restore-filter-name = { COPY(source, "bookmarksRestoreFilterName") }
+places-bookmarks-restore-format-error = { COPY(source, "bookmarksRestoreFormatError") }
+places-bookmarks-restore-parse-error = { COPY(source, "bookmarksRestoreParseError") }
+places-bookmarks-import = { COPY(source, "SelectImport") }
+places-bookmarks-export = { COPY(source, "EnterExport") }
+""",
+ source=source,
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1831851_accounts.py b/python/l10n/fluent_migrations/bug_1831851_accounts.py
new file mode 100644
index 0000000000..d50a0192c8
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1831851_accounts.py
@@ -0,0 +1,195 @@
+# 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, PLURALS, REPLACE, REPLACE_IN_TEXT
+
+
+def migrate(ctx):
+ """Bug 1831851 - Migrate accounts.properties to Fluent, part {index}."""
+
+ accounts = "browser/chrome/browser/accounts.properties"
+ accounts_ftl = "browser/browser/accounts.ftl"
+ preferences_ftl = "browser/browser/preferences/preferences.ftl"
+
+ ctx.add_transforms(
+ accounts_ftl,
+ accounts_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("account-reconnect"),
+ value=REPLACE(
+ accounts,
+ "reconnectDescription",
+ {"%1$S": VARIABLE_REFERENCE("email")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-verify"),
+ value=REPLACE(
+ accounts, "verifyDescription", {"%1$S": VARIABLE_REFERENCE("email")}
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-send-to-all-devices-titlecase"),
+ value=COPY(accounts, "sendToAllDevices.menuitem"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-manage-devices-titlecase"),
+ value=COPY(accounts, "manageDevices.menuitem"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-send-tab-to-device-singledevice-status"),
+ value=COPY(accounts, "sendTabToDevice.singledevice.status"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-send-tab-to-device-singledevice-learnmore"),
+ value=COPY(accounts, "sendTabToDevice.singledevice"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-send-tab-to-device-connectdevice"),
+ value=COPY(accounts, "sendTabToDevice.connectdevice"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-send-tab-to-device-verify-status"),
+ value=COPY(accounts, "sendTabToDevice.verify.status"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-send-tab-to-device-verify"),
+ value=COPY(accounts, "sendTabToDevice.verify"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-connection-title"),
+ value=FTL.Pattern(
+ [
+ FTL.Placeable(
+ FTL.TermReference(
+ id=FTL.Identifier("fxaccount-brand-name"),
+ arguments=FTL.CallArguments(
+ named=[
+ FTL.NamedArgument(
+ FTL.Identifier("capitalization"),
+ FTL.StringLiteral("title"),
+ )
+ ]
+ ),
+ )
+ )
+ ]
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-connection-connected-with"),
+ value=REPLACE(
+ accounts,
+ "otherDeviceConnectedBody",
+ {"%1$S": VARIABLE_REFERENCE("deviceName")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-connection-connected-with-noname"),
+ value=COPY(accounts, "otherDeviceConnectedBody.noDeviceName"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-connection-connected"),
+ value=COPY(accounts, "thisDeviceConnectedBody"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-connection-disconnected"),
+ value=COPY(accounts, "thisDeviceDisconnectedBody"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-single-tab-arriving-title"),
+ value=COPY(accounts, "tabArrivingNotification.title"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-single-tab-arriving-from-device-title"),
+ value=REPLACE(
+ accounts,
+ "tabArrivingNotificationWithDevice.title",
+ {"%1$S": VARIABLE_REFERENCE("deviceName")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-single-tab-arriving-truncated-url"),
+ value=REPLACE(
+ accounts,
+ "singleTabArrivingWithTruncatedURL.body",
+ {"%1$S": VARIABLE_REFERENCE("url")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-multiple-tabs-arriving-title"),
+ value=COPY(accounts, "multipleTabsArrivingNotification.title"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-multiple-tabs-arriving-from-single-device"),
+ value=PLURALS(
+ accounts,
+ "unnamedTabsArrivingNotification2.body",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": VARIABLE_REFERENCE("tabCount"),
+ "#2": VARIABLE_REFERENCE("deviceName"),
+ },
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier(
+ "account-multiple-tabs-arriving-from-multiple-devices"
+ ),
+ value=PLURALS(
+ accounts,
+ "unnamedTabsArrivingNotificationMultiple2.body",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("account-multiple-tabs-arriving-from-unknown-device"),
+ value=PLURALS(
+ accounts,
+ "unnamedTabsArrivingNotificationNoDevice.body",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ ),
+ ],
+ )
+
+ ctx.add_transforms(
+ preferences_ftl,
+ preferences_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("sync-verification-sent-title"),
+ value=COPY(accounts, "verificationSentTitle"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("sync-verification-sent-body"),
+ value=REPLACE(
+ accounts,
+ "verificationSentBody",
+ {"%1$S": VARIABLE_REFERENCE("email")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("sync-verification-not-sent-title"),
+ value=COPY(accounts, "verificationNotSentTitle"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("sync-verification-not-sent-body"),
+ value=COPY(accounts, "verificationNotSentBody"),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1831872_sync.py b/python/l10n/fluent_migrations/bug_1831872_sync.py
new file mode 100644
index 0000000000..f66c27bf5a
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1831872_sync.py
@@ -0,0 +1,31 @@
+# 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 REPLACE
+
+
+def migrate(ctx):
+ """Bug 1831872 - Migrate sync.properties to Fluent, part {index}."""
+
+ source = "services/sync/sync.properties"
+ target = "toolkit/services/accounts.ftl"
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("account-client-name"),
+ value=REPLACE(
+ source,
+ "client.name2",
+ {
+ "%1$S": VARIABLE_REFERENCE("user"),
+ "%2$S": TERM_REFERENCE("brand-short-name"),
+ "%3$S": VARIABLE_REFERENCE("system"),
+ },
+ ),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1832138_ctrlTab.py b/python/l10n/fluent_migrations/bug_1832138_ctrlTab.py
new file mode 100644
index 0000000000..195e3cee49
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1832138_ctrlTab.py
@@ -0,0 +1,37 @@
+# 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 PLURALS, REPLACE_IN_TEXT
+
+
+def migrate(ctx):
+ """Bug 1832138 - Convert browser-ctrlTab.js to Fluent, part {index}."""
+
+ browser = "browser/chrome/browser/browser.properties"
+ target = "browser/browser/tabbrowser.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("tabbrowser-ctrl-tab-list-all-tabs"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ browser,
+ "ctrlTab.listAllTabs.label",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1832141_recently_closed.py b/python/l10n/fluent_migrations/bug_1832141_recently_closed.py
new file mode 100644
index 0000000000..530f6f05dd
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1832141_recently_closed.py
@@ -0,0 +1,76 @@
+import fluent.syntax.ast as FTL
+from fluent.migrate import COPY_PATTERN, PLURALS, REPLACE_IN_TEXT
+from fluent.migrate.helpers import VARIABLE_REFERENCE
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+class CUSTOM_PLURALS(PLURALS):
+ def __call__(self, ctx):
+ pattern = super().__call__(ctx)
+ el = pattern.elements[0]
+ if isinstance(el, FTL.Placeable) and isinstance(
+ el.expression, FTL.SelectExpression
+ ):
+ selexp = el.expression
+ else:
+ selexp = FTL.SelectExpression(
+ VARIABLE_REFERENCE("tabCount"),
+ [FTL.Variant(FTL.Identifier("other"), pattern, default=True)],
+ )
+ pattern = FTL.Pattern([FTL.Placeable(selexp)])
+ selexp.variants[0:0] = [
+ FTL.Variant(
+ FTL.NumberLiteral("0"),
+ FTL.Pattern([FTL.Placeable(VARIABLE_REFERENCE("winTitle"))]),
+ )
+ ]
+ return pattern
+
+
+def migrate(ctx):
+ """Bug 1832141 - Migrate strings to recentlyClosed.ftl, part {index}."""
+
+ appmenu = "browser/browser/appmenu.ftl"
+ browser = "browser/chrome/browser/browser.properties"
+ menubar = "browser/browser/menubar.ftl"
+ target = "browser/browser/recentlyClosed.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("recently-closed-menu-reopen-all-tabs"),
+ value=COPY_PATTERN(menubar, "menu-history-reopen-all-tabs"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("recently-closed-menu-reopen-all-windows"),
+ value=COPY_PATTERN(menubar, "menu-history-reopen-all-windows"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("recently-closed-panel-reopen-all-tabs"),
+ value=COPY_PATTERN(appmenu, "appmenu-reopen-all-tabs"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("recently-closed-panel-reopen-all-windows"),
+ value=COPY_PATTERN(appmenu, "appmenu-reopen-all-windows"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("recently-closed-undo-close-window-label"),
+ value=CUSTOM_PLURALS(
+ browser,
+ "menuUndoCloseWindowLabel",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": VARIABLE_REFERENCE("winTitle"),
+ "#2": VARIABLE_REFERENCE("tabCount"),
+ },
+ ),
+ ),
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1832179_sendTabToDevice.py b/python/l10n/fluent_migrations/bug_1832179_sendTabToDevice.py
new file mode 100644
index 0000000000..526b8a90e0
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1832179_sendTabToDevice.py
@@ -0,0 +1,37 @@
+# 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 PLURALS, REPLACE_IN_TEXT
+
+
+def migrate(ctx):
+ """Bug 1832179 - Convert sendTabsToDevice.label string to Fluent, part {index}."""
+
+ browser = "browser/chrome/browser/browser.properties"
+ target = "browser/browser/sync.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("fxa-menu-send-tab-to-device"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=PLURALS(
+ browser,
+ "sendTabsToDevice.label",
+ VARIABLE_REFERENCE("tabCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {"#1": VARIABLE_REFERENCE("tabCount")},
+ ),
+ ),
+ )
+ ],
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1832186_popupwarning.py b/python/l10n/fluent_migrations/bug_1832186_popupwarning.py
new file mode 100644
index 0000000000..4239899725
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1832186_popupwarning.py
@@ -0,0 +1,139 @@
+# 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,
+ PLURALS,
+ REPLACE,
+ REPLACE_IN_TEXT,
+)
+
+
+def migrate(ctx):
+ """Bug 1832186 - Migrate popup warning strings to Fluent, part {index}."""
+
+ source = "browser/chrome/browser/browser.properties"
+ target = "browser/browser/browser.ftl"
+
+ ctx.add_transforms(
+ target,
+ target,
+ [
+ FTL.Message(
+ id=FTL.Identifier("popup-warning-message"),
+ value=PLURALS(
+ source,
+ "popupWarning.message",
+ VARIABLE_REFERENCE("popupCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": TERM_REFERENCE("brand-short-name"),
+ "#2": VARIABLE_REFERENCE("popupCount"),
+ },
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("popup-warning-exceeded-message"),
+ value=PLURALS(
+ source,
+ "popupWarning.exceeded.message",
+ VARIABLE_REFERENCE("popupCount"),
+ foreach=lambda n: REPLACE_IN_TEXT(
+ n,
+ {
+ "#1": TERM_REFERENCE("brand-short-name"),
+ "#2": VARIABLE_REFERENCE("popupCount"),
+ },
+ ),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("popup-warning-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=FTL.Pattern(
+ [
+ FTL.Placeable(
+ FTL.SelectExpression(
+ selector=FTL.FunctionReference(
+ FTL.Identifier("PLATFORM"),
+ FTL.CallArguments(),
+ ),
+ variants=[
+ FTL.Variant(
+ key=FTL.Identifier("windows"),
+ value=COPY(
+ source, "popupWarningButton"
+ ),
+ ),
+ FTL.Variant(
+ key=FTL.Identifier("other"),
+ value=COPY(
+ source, "popupWarningButtonUnix"
+ ),
+ default=True,
+ ),
+ ],
+ )
+ )
+ ]
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=FTL.Pattern(
+ [
+ FTL.Placeable(
+ FTL.SelectExpression(
+ selector=FTL.FunctionReference(
+ FTL.Identifier("PLATFORM"),
+ FTL.CallArguments(),
+ ),
+ variants=[
+ FTL.Variant(
+ key=FTL.Identifier("windows"),
+ value=COPY(
+ source,
+ "popupWarningButton.accesskey",
+ ),
+ ),
+ FTL.Variant(
+ key=FTL.Identifier("other"),
+ value=COPY(
+ source,
+ "popupWarningButtonUnix.accesskey",
+ ),
+ default=True,
+ ),
+ ],
+ )
+ )
+ ]
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("popup-show-popup-menuitem"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=REPLACE(
+ source,
+ "popupShowPopupPrefix",
+ {
+ "%1$S": VARIABLE_REFERENCE("popupURI"),
+ "‘": FTL.TextElement("“"),
+ "’": FTL.TextElement("”"),
+ },
+ ),
+ )
+ ],
+ ),
+ ],
+ )
diff --git a/python/l10n/fluent_migrations/bug_1832668_firefoxView_navigation.py b/python/l10n/fluent_migrations/bug_1832668_firefoxView_navigation.py
new file mode 100644
index 0000000000..c742f64b36
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1832668_firefoxView_navigation.py
@@ -0,0 +1,27 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate import COPY
+
+
+def migrate(ctx):
+ """Bug 1832668 - Add new side navigation component to Firefox View Next page, part {index}."""
+ ctx.add_transforms(
+ "browser/browser/firefoxView.ftl",
+ "browser/browser/firefoxView.ftl",
+ transforms_from(
+ """
+firefoxview-overview-nav = {COPY_PATTERN(from_path, "firefoxview-overview-navigation")}
+ .title = {COPY_PATTERN(from_path, "firefoxview-overview-navigation")}
+firefoxview-history-nav = {COPY_PATTERN(from_path, "firefoxview-history-navigation")}
+ .title = {COPY_PATTERN(from_path, "firefoxview-history-navigation")}
+firefoxview-opentabs-nav = {COPY_PATTERN(from_path, "firefoxview-opentabs-navigation")}
+ .title = {COPY_PATTERN(from_path, "firefoxview-opentabs-navigation")}
+ """,
+ from_path="browser/browser/firefoxView.ftl",
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1833228_fxviewTabList.py b/python/l10n/fluent_migrations/bug_1833228_fxviewTabList.py
new file mode 100644
index 0000000000..43d0d8834c
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1833228_fxviewTabList.py
@@ -0,0 +1,44 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate import COPY
+
+
+def migrate(ctx):
+ """Bug 1833228 - Remove "moz-" from moz-tab-list component and associated .ftl, .css files, part {index}."""
+ ctx.add_transforms(
+ "browser/browser/fxviewTabList.ftl",
+ "browser/browser/fxviewTabList.ftl",
+ transforms_from(
+ """
+fxviewtabrow-open-menu-button =
+ .title = {COPY_PATTERN(from_path, "mztabrow-open-menu-button.title")}
+fxviewtabrow-date = {COPY_PATTERN(from_path, "mztabrow-date")}
+fxviewtabrow-time = {COPY_PATTERN(from_path, "mztabrow-time")}
+fxviewtabrow-tabs-list-tab =
+ .title = {COPY_PATTERN(from_path, "mztabrow-tabs-list-tab.title")}
+fxviewtabrow-dismiss-tab-button =
+ .title = {COPY_PATTERN(from_path, "mztabrow-dismiss-tab-button.title")}
+fxviewtabrow-just-now-timestamp = {COPY_PATTERN(from_path, "mztabrow-just-now-timestamp")}
+fxviewtabrow-delete = {COPY_PATTERN(from_path, "mztabrow-delete")}
+ .accesskey = {COPY_PATTERN(from_path, "mztabrow-delete.accesskey")}
+fxviewtabrow-forget-about-this-site = {COPY_PATTERN(from_path, "mztabrow-forget-about-this-site")}
+ .accesskey = {COPY_PATTERN(from_path, "mztabrow-forget-about-this-site.accesskey")}
+fxviewtabrow-open-in-window = {COPY_PATTERN(from_path, "mztabrow-open-in-window")}
+ .accesskey = {COPY_PATTERN(from_path, "mztabrow-open-in-window.accesskey")}
+fxviewtabrow-open-in-private-window = {COPY_PATTERN(from_path, "mztabrow-open-in-private-window")}
+ .accesskey = {COPY_PATTERN(from_path, "mztabrow-open-in-private-window.accesskey")}
+fxviewtabrow-add-bookmark = {COPY_PATTERN(from_path, "mztabrow-add-bookmark")}
+ .accesskey = {COPY_PATTERN(from_path, "mztabrow-add-bookmark.accesskey")}
+fxviewtabrow-save-to-pocket = {COPY_PATTERN(from_path, "mztabrow-save-to-pocket")}
+ .accesskey = {COPY_PATTERN(from_path, "mztabrow-save-to-pocket.accesskey")}
+fxviewtabrow-copy-link = {COPY_PATTERN(from_path, "mztabrow-copy-link")}
+ .accesskey = {COPY_PATTERN(from_path, "mztabrow-copy-link.accesskey")}
+ """,
+ from_path="browser/browser/mozTabList.ftl",
+ ),
+ )
diff --git a/python/l10n/fluent_migrations/bug_1835559_aboutDialog_explicit_textContent.py b/python/l10n/fluent_migrations/bug_1835559_aboutDialog_explicit_textContent.py
new file mode 100644
index 0000000000..1d73d62692
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1835559_aboutDialog_explicit_textContent.py
@@ -0,0 +1,67 @@
+# 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 INSERT_VAR_IN_TAG(TransformPattern):
+ def visit_TextElement(self, node):
+ old_str = node.value
+ new_str = old_str
+ # update-downloading / update-downloading-message
+ new_str = new_str.replace(
+ '<label data-l10n-name="download-status"/>',
+ '<label data-l10n-name="download-status">{ $transfer }</label>',
+ )
+ # update-manual / update-internal-error
+ new_str = new_str.replace(
+ '<label data-l10n-name="manual-link"/>',
+ '<label data-l10n-name="manual-link">{ $displayUrl }</label>',
+ )
+ # aboutdialog-update-manual
+ new_str = new_str.replace(
+ '<a data-l10n-name="manual-link"/>',
+ '<a data-l10n-name="manual-link">{ $displayUrl }</a>',
+ )
+ if old_str == new_str and "$" not in old_str:
+ print("Warning: Failed to insert var in link in {}".format(old_str))
+ else:
+ node.value = new_str
+ return node
+
+
+def migrate(ctx):
+ """Bug 1835559, insert textContent var in a/label elements, part {index}."""
+
+ aboutDialog_ftl = "browser/browser/aboutDialog.ftl"
+ ctx.add_transforms(
+ aboutDialog_ftl,
+ aboutDialog_ftl,
+ [
+ FTL.Message(
+ id=FTL.Identifier("settings-update-downloading"),
+ value=INSERT_VAR_IN_TAG(aboutDialog_ftl, "update-downloading"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("aboutdialog-update-downloading"),
+ value=INSERT_VAR_IN_TAG(aboutDialog_ftl, "update-downloading-message"),
+ ),
+ # Note: while we're renaming anyway: strip "aboutdialog-" prefix
+ # because it is used in main.inc.xhtml, not aboutDialog.xhtml.
+ FTL.Message(
+ id=FTL.Identifier("settings-update-manual-with-link"),
+ value=INSERT_VAR_IN_TAG(aboutDialog_ftl, "aboutdialog-update-manual"),
+ ),
+ # This is the actual update-manual message in aboutDialog.xhtml.
+ FTL.Message(
+ id=FTL.Identifier("aboutdialog-update-manual-with-link"),
+ value=INSERT_VAR_IN_TAG(aboutDialog_ftl, "update-manual"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("update-internal-error2"),
+ value=INSERT_VAR_IN_TAG(aboutDialog_ftl, "update-internal-error"),
+ ),
+ ],
+ )
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/mozxchannel/__init__.py b/python/l10n/mozxchannel/__init__.py
new file mode 100644
index 0000000000..66ee3966ca
--- /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",
+ "esr102": "releases/mozilla-esr102",
+ },
+ "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..23d8120a3c
--- /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(br"^\s*basepath\s*=\s*.+", base_line, toml_content, flags=re.M)
+
+ # process [[paths]]
+ start = 0
+ content2 = b""
+ for m in re.finditer(
+ br"\[\[\s*paths\s*\]\].+?(?=\[|\Z)", content1, re.M | re.DOTALL
+ ):
+ content2 += content1[start : m.start()]
+ path_content = m.group()
+ l10n_line = re.search(br"^\s*l10n\s*=.*$", path_content, flags=re.M).group()
+ # remove variable expansions
+ new_reference = re.sub(br"{\s*\S+\s*}", b"", l10n_line)
+ # make the l10n a reference line
+ new_reference = re.sub(br"^(\s*)l10n(\s*=)", br"\1reference\2", new_reference)
+ content2 += re.sub(
+ br"^\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(
+ br"\[\[\s*includes\s*\]\].+?(?=\[|\Z)", content2, re.M | re.DOTALL
+ ):
+ content3 += content2[start : m.start()]
+ include_content = m.group()
+ m_ = re.search(br'^\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..150a942e78
--- /dev/null
+++ b/python/l10n/test_fluent_migrations/fmt.py
@@ -0,0 +1,188 @@
+import codecs
+import logging
+import os
+import re
+import shutil
+import sys
+from difflib import unified_diff
+
+import hglib
+import mozpack.path as mozpath
+from compare_locales.merge import merge_channels
+from compare_locales.paths.configparser import TOMLParser
+from compare_locales.paths.files import ProjectFiles
+from fluent.migrate import validator
+from fluent.syntax import FluentParser, FluentSerializer
+from mach.util import get_state_dir
+
+
+def inspect_migration(path):
+ """Validate recipe and extract some metadata."""
+ return validator.Validator.validate(path)
+
+
+def prepare_object_dir(cmd):
+ """Prepare object dir to have an up-to-date clone of gecko-strings.
+
+ We run this once per mach invocation, for all tested migrations.
+ """
+ obj_dir = mozpath.join(cmd.topobjdir, "python", "l10n")
+ if not os.path.exists(obj_dir):
+ os.makedirs(obj_dir)
+ state_dir = get_state_dir()
+ if os.path.exists(mozpath.join(state_dir, "gecko-strings")):
+ cmd.run_process(
+ ["hg", "pull", "-u"], cwd=mozpath.join(state_dir, "gecko-strings")
+ )
+ else:
+ cmd.run_process(
+ ["hg", "clone", "https://hg.mozilla.org/l10n/gecko-strings"],
+ cwd=state_dir,
+ )
+ return obj_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, to_test, references):
+ """Test the given recipe.
+
+ This creates a workdir by l10n-merging gecko-strings and the m-c source,
+ to mimmic 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 = mozpath.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(mozpath.join(work_dir, "reference"))
+ l10n_toml = mozpath.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])
+ for ref in references:
+ if ref != mozpath.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 = mozpath.join(work_dir, "reference", ref)
+ m = files.match(full_ref)
+ if m is None:
+ raise ValueError("Bad reference path: " + ref)
+ m_c_path = m[1]
+ g_s_path = mozpath.join(work_dir, "gecko-strings", 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))
+ client = hglib.clone(
+ source=mozpath.join(get_state_dir(), "gecko-strings"),
+ dest=mozpath.join(work_dir, "en-US"),
+ )
+ client.open()
+ old_tip = client.tip().node
+ run_migration = [
+ cmd._virtualenv_manager.python_path,
+ "-m",
+ "fluent.migrate.tool",
+ "--lang",
+ "en-US",
+ "--reference-dir",
+ mozpath.join(work_dir, "reference"),
+ "--localization-dir",
+ mozpath.join(work_dir, "en-US"),
+ "--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.tip().node
+ 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(
+ mozpath.join(work_dir, "reference", ref),
+ mozpath.join(work_dir, "en-US", ref),
+ )
+ messages = [
+ l.desc.decode("utf-8") for l in client.log(b"::%s - ::%s" % (tip, old_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