summaryrefslogtreecommitdiffstats
path: root/comm/python/l10n
diff options
context:
space:
mode:
Diffstat (limited to 'comm/python/l10n')
-rw-r--r--comm/python/l10n/l10n_clone/l10n_clone.py96
-rw-r--r--comm/python/l10n/mach_commands.py264
-rw-r--r--comm/python/l10n/missing_ftl/__init__.py59
-rw-r--r--comm/python/l10n/tb_fluent_migrations/__init__.py0
-rw-r--r--comm/python/l10n/tb_fluent_migrations/bug_1827199_multi_message_view.py33
-rw-r--r--comm/python/l10n/tb_fluent_migrations/bug_1831422_backupKeyPassword.py25
-rw-r--r--comm/python/l10n/tb_fluent_migrations/bug_1833042_unfied_toolbar_button_style.py25
-rw-r--r--comm/python/l10n/tb_fluent_migrations/bug_1838109_changeExpiryDlg.py27
-rw-r--r--comm/python/l10n/tb_fluent_migrations/bug_1838770_properties_menu_item.py56
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_ics_file_dialog.py20
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_uri_redirect_dialog.py20
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1731837_delete_commands.py114
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1798509_unified_toolbar_customization.py52
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1803705_thread_pane.py111
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1805746_calendar_view.py29
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1805938_calendar_recurrence_ux.py25
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1806567_quick_filter_bar_migration.py182
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1809312_unified_toolbar_buttons.py218
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1811400_thread_pane_column_picker.py79
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1814393_unified_toolbar_customization_tabs.py36
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1814664_unified_toolbar_calendar_items.py48
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1815489_add_back_forward_and_stop.py33
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1815605_calendar_context_menu_has_empty_items.py52
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1816532_about_dialog_migration.py94
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1816593_flexbox_emulation_dialogs.py21
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1817914_tags_mode.py21
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1817915_get_new_messages.py22
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1820700_select_thread.py35
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1823033_activity_indicator.py23
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1827261_add_enable_disable_compact_mode_options_to_folder_mode_context_menu.py26
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1827891_dnt_prefs_learn_more.py21
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1828340_aboutdialog_layout_fixes.py42
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1830004_folder_quota.py28
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_cal_enable78.py30
-rw-r--r--comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_extensions_to_fluent.py531
-rw-r--r--comm/python/l10n/tbxchannel/__init__.py47
-rw-r--r--comm/python/l10n/tbxchannel/l10n_merge.py26
-rw-r--r--comm/python/l10n/tbxchannel/quarantine_to_strings.py194
-rw-r--r--comm/python/l10n/tbxchannel/tb_migration_test.py172
39 files changed, 2937 insertions, 0 deletions
diff --git a/comm/python/l10n/l10n_clone/l10n_clone.py b/comm/python/l10n/l10n_clone/l10n_clone.py
new file mode 100644
index 0000000000..899ad4a212
--- /dev/null
+++ b/comm/python/l10n/l10n_clone/l10n_clone.py
@@ -0,0 +1,96 @@
+# 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/.
+"""
+Download and combine translations from l10n-central and comm-l10n for
+use by mach build installers-$AB_CD and mach build langpack-$AB_CD.
+"""
+
+import argparse
+import json
+import os
+import sys
+import tempfile
+from pathlib import Path
+
+from mozpack.copier import FileCopier
+from mozpack.files import FileFinder
+from mozversioncontrol.repoupdate import update_mercurial_repo
+
+COMM_PATH = (Path(__file__).parent / "../../..").resolve()
+GECKO_PATH = COMM_PATH.parent
+COMM_PYTHON_L10N = os.path.join(COMM_PATH, "python/l10n")
+sys.path.insert(1, COMM_PYTHON_L10N)
+
+from tbxchannel.l10n_merge import (
+ COMM_L10N,
+ COMM_STRINGS_PATTERNS,
+ GECKO_STRINGS_PATTERNS,
+ L10N_CENTRAL,
+)
+
+ALL_LOCALES = [l.rstrip() for l in (COMM_PATH / "mail/locales/all-locales").open().readlines()]
+
+
+def tb_locale(locale):
+ if locale in ALL_LOCALES:
+ return locale
+ raise argparse.ArgumentTypeError("Locale {} invalid.".format(locale))
+
+
+def get_revision(project, locale):
+ json_file = {
+ "browser": GECKO_PATH / "browser/locales/l10n-changesets.json",
+ "mail": COMM_PATH / "mail/locales/l10n-changesets.json",
+ }.get(project)
+ if json_file is None:
+ raise Exception(f"Invalid project {project} for l10n-changesets.json!")
+
+ with open(json_file) as fp:
+ changesets = json.load(fp)
+
+ revision = changesets.get(locale, {}).get("revision")
+ if revision is None:
+ raise Exception(f"Locale {locale} not found in {project} l10n-changesets.json!")
+
+ return revision
+
+
+def get_strings_repos(locale, destination):
+ with tempfile.TemporaryDirectory() as tmproot:
+ central_url = "{}/{}".format(L10N_CENTRAL, locale)
+ l10n_central = Path(tmproot) / "l10n-central"
+ l10n_central.mkdir()
+ central_path = l10n_central / locale
+ central_revision = get_revision("browser", locale)
+ update_mercurial_repo("hg", central_url, central_path, revision=central_revision)
+
+ comm_l10n = Path(tmproot) / "comm-l10n"
+ comm_revision = get_revision("mail", locale)
+ update_mercurial_repo("hg", COMM_L10N, comm_l10n, revision=comm_revision)
+
+ file_copier = FileCopier()
+
+ def add_to_registry(base_path, patterns):
+ finder = FileFinder(base_path)
+ for pattern in patterns:
+ for _filepath, _fileobj in finder.find(pattern.format(lang=locale)):
+ file_copier.add(_filepath, _fileobj)
+
+ add_to_registry(l10n_central, GECKO_STRINGS_PATTERNS)
+ add_to_registry(comm_l10n, COMM_STRINGS_PATTERNS)
+
+ file_copier.copy(str(destination))
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Download translated strings from comm-l10n")
+ parser.add_argument("locale", help="The locale to download", type=tb_locale)
+ parser.add_argument("dest_path", help="Path where locale will be downloaded to.", type=Path)
+
+ args = parser.parse_args()
+ get_strings_repos(args.locale, args.dest_path)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/comm/python/l10n/mach_commands.py b/comm/python/l10n/mach_commands.py
new file mode 100644
index 0000000000..e4cfbfcf30
--- /dev/null
+++ b/comm/python/l10n/mach_commands.py
@@ -0,0 +1,264 @@
+# 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 argparse
+import logging
+import os.path
+from pathlib import Path
+
+from mutlh.decorators import Command, CommandArgument
+
+
+# https://stackoverflow.com/a/14117511
+def _positive_int(value):
+ value = int(value)
+ if value <= 0:
+ raise argparse.ArgumentTypeError(f"{value} must be a positive integer.")
+ return value
+
+
+def _retry_run_process(command_context, *args, error_msg=None, **kwargs):
+ try:
+ return command_context.run_process(*args, **kwargs)
+ except Exception as exc:
+ raise Exception(error_msg or str(exc)) from exc
+
+
+def _get_rev(command_context, strings_path):
+ result = []
+
+ def save_output(line):
+ result.append(line)
+
+ status = _retry_run_process(
+ command_context,
+ [
+ "hg",
+ "--cwd",
+ str(strings_path),
+ "log",
+ "-r",
+ ".",
+ "--template",
+ "{node}\n",
+ ],
+ line_handler=save_output,
+ )
+ if status == 0:
+ return "\n".join(result)
+ raise Exception(f"Failed to get head revision: {status}")
+
+
+@Command(
+ "tb-l10n-x-channel",
+ category="thunderbird",
+ description="Create cross-channel content for Thunderbird (comm-strings).",
+)
+@CommandArgument(
+ "--strings-path",
+ "-s",
+ metavar="en-US",
+ type=Path,
+ default=Path("en-US"),
+ help="Path to mercurial repository for comm-strings-quarantine",
+)
+@CommandArgument(
+ "--outgoing-path",
+ "-o",
+ type=Path,
+ help="create an outgoing() patch if there are changes",
+)
+@CommandArgument(
+ "--attempts",
+ type=_positive_int,
+ default=1,
+ help="Number of times to try (for automation)",
+)
+@CommandArgument(
+ "--ssh-secret",
+ action="store",
+ help="Taskcluster secret to use to push (for automation)",
+)
+@CommandArgument(
+ "actions",
+ choices=("prep", "create", "push", "clean"),
+ nargs="+",
+ # This help block will be poorly formatted until we fix bug 1714239
+ help="""
+ "prep": clone repos and pull heads.
+ "create": create the en-US strings commit an optionally create an
+ outgoing() patch.
+ "push": push the en-US strings to the quarantine repo.
+ "clean": clean up any sub-repos.
+ """,
+)
+def tb_cross_channel(
+ command_context,
+ strings_path,
+ outgoing_path,
+ actions,
+ attempts,
+ ssh_secret,
+ **kwargs,
+):
+ """Run Thunderbird's l10n cross-channel content generation."""
+ from tbxchannel import TB_XC_NOTIFICATION_TMPL, get_thunderbird_xc_config
+ from tbxchannel.l10n_merge import COMM_STRINGS_QUARANTINE
+
+ from rocbuild.notify import email_notification
+
+ kwargs.update(
+ {
+ "strings_path": strings_path,
+ "outgoing_path": outgoing_path,
+ "actions": actions,
+ "attempts": attempts,
+ "ssh_secret": ssh_secret,
+ "get_config": get_thunderbird_xc_config,
+ }
+ )
+ command_context._mach_context.commands.dispatch(
+ "l10n-cross-channel", command_context._mach_context, **kwargs
+ )
+ if os.path.exists(outgoing_path):
+ head_rev = _get_rev(command_context, strings_path)
+ rev_url = f"{COMM_STRINGS_QUARANTINE}/rev/{head_rev}"
+
+ notification_body = TB_XC_NOTIFICATION_TMPL.format(rev_url=rev_url)
+ email_notification("X-channel comm-strings-quarantine updated", notification_body)
+
+
+@Command(
+ "tb-add-missing-ftls",
+ category="thunderbird",
+ description="Add missing FTL files after l10n merge.",
+)
+@CommandArgument(
+ "--merge",
+ type=Path,
+ help="Merge path base",
+)
+@CommandArgument(
+ "locale",
+ type=str,
+ help="Locale code",
+)
+def tb_add_missing_ftls(command_context, merge, locale):
+ """
+ Command to create empty FTL files for incomplete localizations to
+ avoid over-zealous en-US fallback as described in bug 1586984. This
+ mach command is based on the script used to update the l10n-central
+ repositories. It gets around the need to have write access to those
+ repositories in favor of creating the files during l10m-repackaging.
+ This code assumes that mach compare-locales --merge has already run.
+ """
+ from missing_ftl import add_missing_ftls, get_lang_ftls, get_source_ftls
+
+ print("Checking for missing .ftl files in locale {}".format(locale))
+ comm_src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
+ source_files = get_source_ftls(comm_src_dir)
+
+ l10n_path = os.path.join(merge, locale)
+ locale_files = get_lang_ftls(l10n_path)
+
+ add_missing_ftls(l10n_path, source_files, locale_files)
+
+
+@Command(
+ "tb-fluent-migration-test",
+ category="thunderbird",
+ description="Test Fluent migration recipes.",
+)
+@CommandArgument("test_paths", nargs="*", metavar="N", help="Recipe paths to test.")
+def run_migration_tests(command_context, test_paths=None, **kwargs):
+ if not test_paths:
+ test_paths = []
+ command_context.activate_virtualenv()
+
+ from tbxchannel.tb_migration_test import inspect_migration, prepare_object_dir, test_migration
+
+ rv = 0
+ with_context = []
+ for to_test in test_paths:
+ try:
+ context = inspect_migration(to_test)
+ for issue in context["issues"]:
+ command_context.log(
+ logging.ERROR,
+ "tb-fluent-migration-test",
+ {
+ "error": issue["msg"],
+ "file": to_test,
+ },
+ "ERROR in {file}: {error}",
+ )
+ if context["issues"]:
+ continue
+ with_context.append(
+ {
+ "to_test": to_test,
+ "references": context["references"],
+ }
+ )
+ except Exception as e:
+ command_context.log(
+ logging.ERROR,
+ "tb-fluent-migration-test",
+ {"error": str(e), "file": to_test},
+ "ERROR in {file}: {error}",
+ )
+ rv |= 1
+ obj_dir = prepare_object_dir(command_context)
+ for context in with_context:
+ rv |= test_migration(command_context, obj_dir, **context)
+ return rv
+
+
+from mutlh.decorators import Command, CommandArgument
+
+
+@Command(
+ "tb-l10n-quarantine-to-strings",
+ category="thunderbird",
+ description="Publish quarantines strings to comm-l10n.",
+)
+@CommandArgument(
+ "--quarantine-path",
+ "-q",
+ type=Path,
+ help="Path to comm-strings-quarantine repo",
+)
+@CommandArgument(
+ "--comm-l10n-path",
+ "-l",
+ type=Path,
+ help="Path to comm-l10n repo",
+)
+@CommandArgument(
+ "actions",
+ choices=("clean", "prep", "migrate", "push"),
+ nargs="+",
+ # This help block will be poorly formatted until we fix bug 1714239
+ help="""
+ "clean": remove existing clones of quarantine and comm-l10n repos
+ "prep": clone a new repository or update an existing one to latest rev
+ "migrate": update comm-l10n en_US from quarantine
+ "push": push comm-l10n
+ """,
+)
+def quarantine_to_strings(
+ command_context,
+ quarantine_path,
+ comm_l10n_path,
+ actions,
+ **kwargs,
+):
+ """Publish strings in Thunderbird's comm-l10n cross-channel repository from
+ comm-strings-quarantine."""
+ from tbxchannel.quarantine_to_strings import publish_strings
+
+ command_context._set_log_level(True)
+ command_context.activate_virtualenv()
+ command_context.log_manager.enable_unstructured()
+ publish_strings(command_context, quarantine_path, comm_l10n_path, actions, **kwargs)
diff --git a/comm/python/l10n/missing_ftl/__init__.py b/comm/python/l10n/missing_ftl/__init__.py
new file mode 100644
index 0000000000..1e90db6d5b
--- /dev/null
+++ b/comm/python/l10n/missing_ftl/__init__.py
@@ -0,0 +1,59 @@
+# 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
+
+
+def walk_path(folder_path, prefix):
+ file_list = []
+ for root, dirs, files in os.walk(folder_path, followlinks=True):
+ for file_name in files:
+ if os.path.splitext(file_name)[1] == ".ftl":
+ file_name = os.path.relpath(os.path.join(root, file_name), folder_path)
+ file_name = os.path.join(prefix, file_name)
+ file_list.append(file_name)
+ file_list.sort()
+
+ return file_list
+
+
+def get_source_ftls(comm_src_dir):
+ """Find ftl files in en-US mail and calendar."""
+ file_list = []
+ for d in ["mail", "calendar"]:
+ folder_path = os.path.join(comm_src_dir, d, "locales/en-US")
+ file_list += walk_path(folder_path, d)
+ return file_list
+
+
+def get_lang_ftls(l10n_path):
+ """Find ftl files in the merge directory."""
+ file_list = []
+ for d in ["mail", "calendar"]:
+ folder_path = os.path.join(l10n_path, d)
+ file_list += walk_path(folder_path, d)
+ return file_list
+
+
+def add_missing_ftls(l10n_path, source_files, locale_files):
+ """
+ For any ftl files that are in source_files but missing in locale_files,
+ create a placeholder file.
+ """
+ for file_name in source_files:
+ if file_name not in locale_files:
+ full_file_name = os.path.join(l10n_path, file_name)
+ file_path = os.path.dirname(full_file_name)
+ if not os.path.isdir(file_path):
+ # Create missing folder
+ print("Creating missing folder: {}".format(os.path.relpath(file_path, l10n_path)))
+ os.makedirs(file_path)
+
+ print("Adding missing file: {}".format(file_name))
+ with open(full_file_name, "w") as f:
+ f.write(
+ "# This Source Code Form is subject to the terms of the Mozilla Public\n"
+ "# License, v. 2.0. If a copy of the MPL was not distributed with this\n"
+ "# file, You can obtain one at http://mozilla.org/MPL/2.0/.\n"
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/__init__.py b/comm/python/l10n/tb_fluent_migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/__init__.py
diff --git a/comm/python/l10n/tb_fluent_migrations/bug_1827199_multi_message_view.py b/comm/python/l10n/tb_fluent_migrations/bug_1827199_multi_message_view.py
new file mode 100644
index 0000000000..47cc910b67
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/bug_1827199_multi_message_view.py
@@ -0,0 +1,33 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb import COPY_PATTERN
+from fluent.migratetb.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1827199 - Message summary preview header buttons are not keyboard accessible"""
+
+ target = reference = "mail/locales/en-US/messenger/multimessageview.ftl"
+ ctx.add_transforms(
+ target,
+ reference,
+ transforms_from(
+ """
+multi-message-window-title =
+ .title = {{COPY_PATTERN(from_path, "window.title")}}
+
+selected-messages-label =
+ .label = {{COPY_PATTERN(from_path, "selectedmessages.label")}}
+
+multi-message-archive-button =
+ .label = {{COPY_PATTERN(from_path, "archiveButton.label")}}
+ .tooltiptext = {{COPY_PATTERN(from_path, "archiveButton.label")}}
+
+multi-message-delete-button =
+ .label = {{COPY_PATTERN(from_path, "deleteButton.label")}}
+ .tooltiptext = {{COPY_PATTERN(from_path, "deleteButton.label")}}
+ """,
+ from_path="mail/locales/en-US/chrome/messenger/multimessageview.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/bug_1831422_backupKeyPassword.py b/comm/python/l10n/tb_fluent_migrations/bug_1831422_backupKeyPassword.py
new file mode 100644
index 0000000000..1c86a338a4
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/bug_1831422_backupKeyPassword.py
@@ -0,0 +1,25 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb import COPY_PATTERN
+from fluent.migratetb.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1831422 - migrations in OpenPGP key backup dialog"""
+
+ target = reference = "mail/messenger/openpgp/backupKeyPassword.ftl"
+ ctx.add_transforms(
+ target,
+ reference,
+ transforms_from(
+ """
+set-password-window-title = {{COPY_PATTERN(from_path, "set-password-window.title")}}
+
+set-password-backup-pw-label = {{COPY_PATTERN(from_path, "set-password-backup-pw.value")}}
+
+set-password-backup-pw2-label = {{COPY_PATTERN(from_path, "set-password-repeat-backup-pw.value")}}
+ """,
+ from_path="mail/messenger/openpgp/backupKeyPassword.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/bug_1833042_unfied_toolbar_button_style.py b/comm/python/l10n/tb_fluent_migrations/bug_1833042_unfied_toolbar_button_style.py
new file mode 100644
index 0000000000..108f48474e
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/bug_1833042_unfied_toolbar_button_style.py
@@ -0,0 +1,25 @@
+# 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_PATTERN
+
+
+def migrate(ctx):
+ """Bug 1833042 - Don't automatically update the button style preference when changed in the unified toolbar customization panel, part {index}."""
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbar.ftl",
+ "mail/messenger/unifiedToolbar.ftl",
+ transforms_from(
+ """
+customize-button-style-icons-beside-text-option = {COPY_PATTERN(from_path, "customize-button-style-icons-beside-text.label")}
+
+customize-button-style-icons-above-text-option = {COPY_PATTERN(from_path, "customize-button-style-icons-above-text.label")}
+
+customize-button-style-icons-only-option = {COPY_PATTERN(from_path, "customize-button-style-icons-only.label")}
+
+customize-button-style-text-only-option = {COPY_PATTERN(from_path, "customize-button-style-text-only.label")}
+ """,
+ from_path="mail/messenger/unifiedToolbar.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/bug_1838109_changeExpiryDlg.py b/comm/python/l10n/tb_fluent_migrations/bug_1838109_changeExpiryDlg.py
new file mode 100644
index 0000000000..68566c4131
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/bug_1838109_changeExpiryDlg.py
@@ -0,0 +1,27 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb import COPY_PATTERN
+from fluent.migratetb.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1838109 - migrate some strings in changeExpiryDlg.xhtml"""
+
+ target = reference = "mail/messenger/openpgp/changeExpiryDlg.ftl"
+ ctx.add_transforms(
+ target,
+ reference,
+ transforms_from(
+ """
+openpgp-change-expiry-title = {{COPY_PATTERN(from_path, "openpgp-change-key-expiry-title.title")}}
+
+expire-no-change-label = {{COPY_PATTERN(from_path, "expire-dont-change.label")}}
+
+expire-in-time-label = {{COPY_PATTERN(from_path, "expire-in-label.label")}}
+
+expire-never-expire-label = {{COPY_PATTERN(from_path, "expire-never-label.label")}}
+ """,
+ from_path="mail/messenger/openpgp/changeExpiryDlg.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/bug_1838770_properties_menu_item.py b/comm/python/l10n/tb_fluent_migrations/bug_1838770_properties_menu_item.py
new file mode 100644
index 0000000000..04e15628b8
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/bug_1838770_properties_menu_item.py
@@ -0,0 +1,56 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb import COPY
+
+import fluent.syntax.ast as FTL
+
+
+def migrate(ctx):
+ """Bug 1838770 - Edit > Folder Properties doesn't work, part {index}."""
+ source = "mail/chrome/messenger/messenger.dtd"
+ dest = "mail/messenger/messenger.ftl"
+ ctx.add_transforms(
+ dest,
+ dest,
+ [
+ FTL.Message(
+ id=FTL.Identifier("menu-edit-properties"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"), value=COPY(source, "folderPropsCmd2.label")
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "folderPropsCmd.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("menu-edit-folder-properties"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "folderPropsFolderCmd2.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "folderPropsCmd.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("menu-edit-newsgroup-properties"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(source, "folderPropsNewsgroupCmd2.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "folderPropsCmd.accesskey"),
+ ),
+ ],
+ ),
+ ],
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_ics_file_dialog.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_ics_file_dialog.py
new file mode 100644
index 0000000000..4e7df08f73
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_ics_file_dialog.py
@@ -0,0 +1,20 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1703164 - convert calendar/base/content/dialogs/calendar-ics-file-dialog.xhtml to html"""
+
+ ctx.add_transforms(
+ "calendar/calendar/calendar-ics-file-dialog.ftl",
+ "calendar/calendar/calendar-ics-file-dialog.ftl",
+ transforms_from(
+ """
+calendar-ics-file-window-title = {{COPY_PATTERN(from_path, "calendar-ics-file-window-2.title")}}
+ """,
+ from_path="calendar/calendar/calendar-ics-file-dialog.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_uri_redirect_dialog.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_uri_redirect_dialog.py
new file mode 100644
index 0000000000..cd4e681da0
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1703164_calendar_uri_redirect_dialog.py
@@ -0,0 +1,20 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1703164 - convert calendar/base/content/dialogs/calendar-uri-redirect-dialog.xhtml to top level html"""
+
+ ctx.add_transforms(
+ "calendar/calendar/calendar-uri-redirect-dialog.ftl",
+ "calendar/calendar/calendar-uri-redirect-dialog.ftl",
+ transforms_from(
+ """
+calendar-uri-redirect-window-title = {{COPY_PATTERN(from_path, "calendar-uri-redirect-window.title")}}
+ """,
+ from_path="calendar/calendar/calendar-uri-redirect-dialog.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1731837_delete_commands.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1731837_delete_commands.py
new file mode 100644
index 0000000000..fc045d1ed6
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1731837_delete_commands.py
@@ -0,0 +1,114 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import re
+
+from fluent.migratetb import COPY, COPY_PATTERN
+from fluent.migratetb.helpers import VARIABLE_REFERENCE
+from fluent.migratetb.transforms import Transform, TransformPattern
+
+import fluent.syntax.ast as FTL
+
+
+def migrate(ctx):
+ """Bug 1731837 - Fix multiple problems with the labelling of Delete commands, part {index}."""
+ source = "mail/chrome/messenger/messenger.dtd"
+ dest = "mail/messenger/messenger.ftl"
+ ctx.add_transforms(
+ dest,
+ dest,
+ [
+ FTL.Message(
+ id=FTL.Identifier("menu-edit-delete-folder"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"), value=COPY(source, "deleteFolderCmd.label")
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "deleteFolderCmd.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("menu-edit-delete-messages"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=Transform.pattern_of(
+ FTL.SelectExpression(
+ selector=VARIABLE_REFERENCE("count"),
+ variants=[
+ FTL.Variant(
+ key=FTL.Identifier("one"),
+ value=COPY(source, "deleteMsgCmd.label"),
+ ),
+ FTL.Variant(
+ key=FTL.Identifier("other"),
+ default=True,
+ value=COPY(source, "deleteMsgsCmd.label"),
+ ),
+ ],
+ )
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "deleteMsgCmd.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("menu-edit-undelete-messages"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=Transform.pattern_of(
+ FTL.SelectExpression(
+ selector=VARIABLE_REFERENCE("count"),
+ variants=[
+ FTL.Variant(
+ key=FTL.Identifier("one"),
+ value=COPY(source, "undeleteMsgCmd.label"),
+ ),
+ FTL.Variant(
+ key=FTL.Identifier("other"),
+ default=True,
+ value=COPY(source, "undeleteMsgsCmd.label"),
+ ),
+ ],
+ )
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(source, "undeleteMsgCmd.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("mail-context-undelete-messages"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=Transform.pattern_of(
+ FTL.SelectExpression(
+ selector=VARIABLE_REFERENCE("count"),
+ variants=[
+ FTL.Variant(
+ key=FTL.Identifier("one"),
+ value=COPY(source, "undeleteMsgCmd.label"),
+ ),
+ FTL.Variant(
+ key=FTL.Identifier("other"),
+ default=True,
+ value=COPY(source, "undeleteMsgsCmd.label"),
+ ),
+ ],
+ )
+ ),
+ )
+ ],
+ ),
+ ],
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1798509_unified_toolbar_customization.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1798509_unified_toolbar_customization.py
new file mode 100644
index 0000000000..7ac296ec35
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1798509_unified_toolbar_customization.py
@@ -0,0 +1,52 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1798509 - Unified toolbar customization fluent migration part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbar.ftl",
+ "mail/messenger/unifiedToolbar.ftl",
+ transforms_from(
+ """
+customize-menu-customize =
+ .label = { COPY(from_path, "customizeToolbar.label") }
+ """,
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbar.ftl",
+ "mail/messenger/unifiedToolbar.ftl",
+ transforms_from(
+ """
+customize-space-mail = { COPY_PATTERN(from_path, "spaces-toolbar-button-mail2.title") }
+
+customize-space-addressbook = { COPY_PATTERN(from_path, "spaces-toolbar-button-address-book2.title") }
+
+customize-space-calendar = { COPY_PATTERN(from_path, "spaces-toolbar-button-calendar2.title") }
+
+customize-space-tasks = { COPY_PATTERN(from_path, "spaces-toolbar-button-tasks2.title") }
+
+customize-space-chat = { COPY_PATTERN(from_path, "spaces-toolbar-button-chat2.title") }
+
+customize-space-settings = { COPY_PATTERN(from_path, "spaces-toolbar-button-settings2.title") }
+ """,
+ from_path="mail/messenger/messenger.ftl",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbar.ftl",
+ "mail/messenger/unifiedToolbar.ftl",
+ transforms_from(
+ """
+customize-button-style-icons-beside-text =
+ .label = { COPY(from_path, "iconsBesideText.label") }
+ """,
+ from_path="mail/chrome/messenger/customizeToolbar.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1803705_thread_pane.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1803705_thread_pane.py
new file mode 100644
index 0000000000..b3f41b1c5a
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1803705_thread_pane.py
@@ -0,0 +1,111 @@
+# 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 1803705 - Migrate thread tree strings to fluent, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/treeView.ftl",
+ "mail/messenger/treeView.ftl",
+ transforms_from(
+ """
+tree-list-view-column-picker =
+ .title = { COPY(from_path, "columnChooser2.tooltip") }
+""",
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
+
+ ctx.add_transforms(
+ "mail/messenger/about3Pane.ftl",
+ "mail/messenger/about3Pane.ftl",
+ transforms_from(
+ """
+threadpane-column-header-select =
+ .title = { COPY(from_path, "selectColumn.tooltip") }
+threadpane-column-label-select =
+ .label = { COPY(from_path, "selectColumn.label") }
+threadpane-column-label-thread =
+ .label = { COPY(from_path, "threadColumn.label") }
+threadpane-column-header-flagged =
+ .title = { COPY(from_path, "starredColumn2.tooltip") }
+threadpane-column-label-flagged =
+ .label = { COPY(from_path, "starredColumn.label") }
+threadpane-column-header-attachments =
+ .title = { COPY(from_path, "attachmentColumn2.tooltip") }
+threadpane-column-label-attachments =
+ .label = { COPY(from_path, "attachmentColumn.label") }
+threadpane-column-header-sender = { COPY(from_path, "fromColumn.label") }
+ .title = { COPY(from_path, "fromColumn2.tooltip") }
+threadpane-column-label-sender =
+ .label = { COPY(from_path, "fromColumn.label") }
+threadpane-column-header-recipient = { COPY(from_path, "recipientColumn.label") }
+ .title = { COPY(from_path, "recipientColumn2.tooltip") }
+threadpane-column-label-recipient =
+ .label = { COPY(from_path, "recipientColumn.label") }
+threadpane-column-header-correspondents = { COPY(from_path, "correspondentColumn.label") }
+ .title = { COPY(from_path, "correspondentColumn2.tooltip") }
+threadpane-column-label-correspondents =
+ .label = { COPY(from_path, "correspondentColumn.label") }
+threadpane-column-header-subject = { COPY(from_path, "subjectColumn.label") }
+ .title = { COPY(from_path, "subjectColumn2.tooltip") }
+threadpane-column-label-subject =
+ .label = { COPY(from_path, "subjectColumn.label") }
+threadpane-column-header-date = { COPY(from_path, "dateColumn.label") }
+ .title = { COPY(from_path, "dateColumn2.tooltip") }
+threadpane-column-label-date =
+ .label = { COPY(from_path, "dateColumn.label") }
+threadpane-column-header-received = { COPY(from_path, "receivedColumn.label") }
+ .title = { COPY(from_path, "receivedColumn2.tooltip") }
+threadpane-column-label-received =
+ .label = { COPY(from_path, "receivedColumn.label") }
+threadpane-column-header-status = { COPY(from_path, "statusColumn.label") }
+ .title = { COPY(from_path, "statusColumn2.tooltip") }
+threadpane-column-label-status =
+ .label = { COPY(from_path, "statusColumn.label") }
+threadpane-column-header-size = { COPY(from_path, "sizeColumn.label") }
+ .title = { COPY(from_path, "sizeColumn2.tooltip") }
+threadpane-column-label-size =
+ .label = { COPY(from_path, "sizeColumn.label") }
+threadpane-column-header-tags = { COPY(from_path, "tagsColumn.label") }
+ .title = { COPY(from_path, "tagsColumn2.tooltip") }
+threadpane-column-label-tags =
+ .label = { COPY(from_path, "tagsColumn.label") }
+threadpane-column-header-account = { COPY(from_path, "accountColumn.label") }
+ .title = { COPY(from_path, "accountColumn2.tooltip") }
+threadpane-column-label-account =
+ .label = { COPY(from_path, "accountColumn.label") }
+threadpane-column-header-priority = { COPY(from_path, "priorityColumn.label") }
+ .title = { COPY(from_path, "priorityColumn2.tooltip") }
+threadpane-column-label-priority =
+ .label = { COPY(from_path, "priorityColumn.label") }
+threadpane-column-header-unread = { COPY(from_path, "unreadColumn.label") }
+ .title = { COPY(from_path, "unreadColumn2.tooltip") }
+threadpane-column-label-unread =
+ .label = { COPY(from_path, "unreadColumn.label") }
+threadpane-column-header-total = { COPY(from_path, "totalColumn.label") }
+ .title = { COPY(from_path, "totalColumn2.tooltip") }
+threadpane-column-label-total =
+ .label = { COPY(from_path, "totalColumn.label") }
+threadpane-column-header-location = { COPY(from_path, "locationColumn.label") }
+ .title = { COPY(from_path, "locationColumn2.tooltip") }
+threadpane-column-label-location =
+ .label = { COPY(from_path, "locationColumn.label") }
+threadpane-column-header-id = { COPY(from_path, "idColumn.label") }
+ .title = { COPY(from_path, "idColumn2.tooltip") }
+threadpane-column-label-id =
+ .label = { COPY(from_path, "idColumn.label") }
+threadpane-column-header-delete =
+ .title = { COPY(from_path, "deleteColumn.tooltip") }
+threadpane-column-label-delete =
+ .label = { COPY(from_path, "deleteColumn.label") }
+""",
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1805746_calendar_view.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1805746_calendar_view.py
new file mode 100644
index 0000000000..eec0fbe44e
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1805746_calendar_view.py
@@ -0,0 +1,29 @@
+# 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 1805746 - Update Calendar View selection part {index}."""
+
+ ctx.add_transforms(
+ "calendar/calendar/calendar-widgets.ftl",
+ "calendar/calendar/calendar-widgets.ftl",
+ transforms_from(
+ """
+calendar-view-toggle-day = { COPY(from_path, "calendar.day.button.label") }
+ .title = { COPY(from_path, "calendar.day.button.tooltip") }
+calendar-view-toggle-week = { COPY(from_path, "calendar.week.button.label") }
+ .title = { COPY(from_path, "calendar.week.button.tooltip") }
+calendar-view-toggle-multiweek = { COPY(from_path, "calendar.multiweek.button.label") }
+ .title = { COPY(from_path, "calendar.multiweek.button.tooltip") }
+calendar-view-toggle-month = { COPY(from_path, "calendar.month.button.label") }
+ .title = { COPY(from_path, "calendar.month.button.tooltip") }
+""",
+ from_path="calendar/chrome/calendar/calendar.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1805938_calendar_recurrence_ux.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1805938_calendar_recurrence_ux.py
new file mode 100644
index 0000000000..715b475015
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1805938_calendar_recurrence_ux.py
@@ -0,0 +1,25 @@
+# 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 1805938 - Refactor recurrence calendar UX part {index}."""
+
+ source = "calendar/chrome/calendar/calendar-event-dialog.dtd"
+ reference = target = "calendar/calendar/calendar-recurrence-dialog.ftl"
+
+ ctx.add_transforms(
+ target,
+ reference,
+ transforms_from(
+ """
+calendar-recurrence-preview-label = { COPY(from_path, "event.recurrence.preview.label") }
+""",
+ from_path=source,
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1806567_quick_filter_bar_migration.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1806567_quick_filter_bar_migration.py
new file mode 100644
index 0000000000..72967ef4d8
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1806567_quick_filter_bar_migration.py
@@ -0,0 +1,182 @@
+# 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 (
+ MESSAGE_REFERENCE,
+ VARIABLE_REFERENCE,
+ transforms_from,
+)
+from fluent.migrate.transforms import (
+ CONCAT,
+ PLURALS,
+ REPLACE,
+ REPLACE_IN_TEXT,
+ Transform,
+)
+
+
+def migrate(ctx):
+ """Bug 1806567 - Migrate Quick Filter Bar strings from DTD to FTL, part {index}"""
+
+ source = "mail/chrome/messenger/quickFilterBar.dtd"
+ target1 = reference1 = "mail/messenger/about3Pane.ftl"
+ ctx.add_transforms(
+ target1,
+ reference1,
+ transforms_from(
+ """
+
+quick-filter-bar-sticky =
+ .title = { COPY(from_path, "quickFilterBar.sticky.tooltip") }
+quick-filter-bar-unread =
+ .title = { COPY(from_path, "quickFilterBar.unread.tooltip") }
+quick-filter-bar-unread-label = { COPY(from_path, "quickFilterBar.unread.label") }
+quick-filter-bar-starred =
+ .title = { COPY(from_path, "quickFilterBar.starred.tooltip") }
+quick-filter-bar-starred-label = { COPY(from_path, "quickFilterBar.starred.label") }
+quick-filter-bar-inaddrbook =
+ .title = { COPY(from_path, "quickFilterBar.inaddrbook.tooltip") }
+quick-filter-bar-inaddrbook-label = { COPY(from_path, "quickFilterBar.inaddrbook.label") }
+quick-filter-bar-tags =
+ .title = { COPY(from_path, "quickFilterBar.tags.tooltip") }
+quick-filter-bar-tags-label = { COPY(from_path, "quickFilterBar.tags.label") }
+quick-filter-bar-attachment =
+ .title = { COPY(from_path, "quickFilterBar.attachment.tooltip") }
+quick-filter-bar-attachment-label = { COPY(from_path, "quickFilterBar.attachment.label") }
+quick-filter-bar-no-results = { COPY(from_path, "quickFilterBar.resultsLabel.none") }
+""",
+ from_path=source,
+ ),
+ )
+ ctx.add_transforms(
+ target1,
+ reference1,
+ [
+ FTL.Message(
+ id=FTL.Identifier("quick-filter-bar-results"),
+ value=PLURALS(
+ source,
+ "quickFilterBar.resultsLabel.some.formatString",
+ VARIABLE_REFERENCE("count"),
+ lambda text: REPLACE_IN_TEXT(text, {"#1": VARIABLE_REFERENCE("count")}),
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("quick-filter-bar-textbox-shortcut"),
+ value=Transform.pattern_of(
+ FTL.SelectExpression(
+ selector=FTL.FunctionReference(
+ id=FTL.Identifier("PLATFORM"),
+ arguments=FTL.CallArguments(),
+ ),
+ variants=[
+ FTL.Variant(
+ key=FTL.Identifier("macos"),
+ default=False,
+ value=REPLACE(
+ source,
+ "quickFilterBar.textbox.emptyText.keyLabel2.mac",
+ {
+ "<": FTL.TextElement(""),
+ "⇧⌘": FTL.TextElement("⇧ ⌘ "),
+ ">": FTL.TextElement(""),
+ },
+ ),
+ ),
+ FTL.Variant(
+ key=FTL.Identifier("other"),
+ default=True,
+ value=REPLACE(
+ source,
+ "quickFilterBar.textbox.emptyText.keyLabel2.nonmac",
+ {
+ "<": FTL.TextElement(""),
+ ">": FTL.TextElement(""),
+ },
+ ),
+ ),
+ ],
+ )
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("quick-filter-bar-textbox"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("placeholder"),
+ value=REPLACE(
+ source,
+ "quickFilterBar.textbox.emptyText.base1",
+ {
+ "#1": CONCAT(
+ FTL.TextElement("<"),
+ MESSAGE_REFERENCE("quick-filter-bar-textbox-shortcut"),
+ FTL.TextElement(">"),
+ )
+ },
+ ),
+ ),
+ ],
+ ),
+ ],
+ )
+ ctx.add_transforms(
+ target1,
+ reference1,
+ transforms_from(
+ """
+quick-filter-bar-boolean-mode =
+ .title = { COPY(from_path, "quickFilterBar.booleanMode.tooltip") }
+quick-filter-bar-boolean-mode-any =
+ .label = { COPY(from_path, "quickFilterBar.booleanModeAny.label") }
+ .title = { COPY(from_path, "quickFilterBar.booleanModeAny.tooltip") }
+quick-filter-bar-boolean-mode-all =
+ .label = { COPY(from_path, "quickFilterBar.booleanModeAll.label") }
+ .title = { COPY(from_path, "quickFilterBar.booleanModeAll.tooltip") }
+quick-filter-bar-text-filter-explanation = { COPY(from_path, "quickFilterBar.textFilter.explanation.label") }
+quick-filter-bar-text-filter-sender = { COPY(from_path, "quickFilterBar.textFilter.sender.label") }
+quick-filter-bar-text-filter-recipients = { COPY(from_path, "quickFilterBar.textFilter.recipients.label") }
+quick-filter-bar-text-filter-subject = { COPY(from_path, "quickFilterBar.textFilter.subject.label") }
+quick-filter-bar-text-filter-body = { COPY(from_path, "quickFilterBar.textFilter.body.label") }
+quick-filter-bar-gloda-upsell-line1 = { COPY(from_path, "quickFilterBar.glodaUpsell.continueSearch") }
+""",
+ from_path=source,
+ ),
+ )
+ ctx.add_transforms(
+ target1,
+ reference1,
+ [
+ FTL.Message(
+ id=FTL.Identifier("quick-filter-bar-gloda-upsell-line2"),
+ value=REPLACE(
+ source,
+ "quickFilterBar.glodaUpsell.pressEnterAndCurrent",
+ {
+ "#1": VARIABLE_REFERENCE("text"),
+ " '": FTL.TextElement(" ‘"),
+ "' ": FTL.TextElement("’ "),
+ },
+ ),
+ ),
+ ],
+ )
+
+ target2 = reference2 = "mail/messenger/messenger.ftl"
+ ctx.add_transforms(
+ target2,
+ reference2,
+ transforms_from(
+ """
+quick-filter-bar-toggle =
+ .label = { COPY(from_path, "quickFilterBar.toggleBarVisibility.menu.label") }
+ .accesskey = { COPY(from_path, "quickFilterBar.toggleBarVisibility.menu.accesskey") }
+quick-filter-bar-show =
+ .key = { COPY(from_path, "quickFilterBar.show.key2") }
+""",
+ from_path=source,
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1809312_unified_toolbar_buttons.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1809312_unified_toolbar_buttons.py
new file mode 100644
index 0000000000..6558f5d6a7
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1809312_unified_toolbar_buttons.py
@@ -0,0 +1,218 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1809312 - Implement mail space actions for unified toolbar fluent migration part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+spacer-label = { COPY(from_path, "springTitle") }
+ """,
+ from_path="mail/chrome/messenger/customizeToolbar.properties",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-write-message-label = { COPY(from_path, "newMsgButton.label") }
+
+toolbar-write-message =
+ .title = { COPY(from_path, "newMsgButton.tooltip") }
+
+toolbar-folder-location-label = { COPY(from_path, "folderLocationToolbarItem.title") }
+
+toolbar-get-messages-label = { COPY(from_path, "getMsgButton1.label") }
+
+toolbar-reply-label = { COPY(from_path, "replyButton.label") }
+
+toolbar-reply =
+ .title = { COPY(from_path, "replyButton.tooltip") }
+
+toolbar-reply-all-label = { COPY(from_path, "replyAllButton.label") }
+
+toolbar-reply-all =
+ .title = { COPY(from_path, "replyAllButton.tooltip") }
+
+toolbar-reply-to-list-label = { COPY(from_path, "replyListButton.label") }
+
+toolbar-reply-to-list =
+ .title = { COPY(from_path, "replyListButton.tooltip") }
+
+toolbar-archive-label = { COPY(from_path, "archiveButton.label") }
+
+toolbar-archive =
+ .title = { COPY(from_path, "archiveButton.tooltip") }
+
+toolbar-conversation-label = { COPY(from_path, "openConversationButton.label") }
+
+toolbar-conversation =
+ .title = { COPY(from_path, "openMsgConversationButton.tooltip") }
+
+toolbar-previous-unread-label = { COPY(from_path, "previousButtonToolbarItem.label") }
+
+toolbar-previous-unread =
+ .title = { COPY(from_path, "previousButton.tooltip") }
+
+toolbar-previous-label = { COPY(from_path, "previousButton.label") }
+
+toolbar-previous =
+ .title = { COPY(from_path, "previousMsgButton.tooltip") }
+
+toolbar-next-unread-label = { COPY(from_path, "nextButtonToolbarItem.label") }
+
+toolbar-next-unread =
+ .title = { COPY(from_path, "nextButton.tooltip") }
+
+toolbar-next-label = { COPY(from_path, "nextMsgButton.label") }
+
+toolbar-next =
+ .title = { COPY(from_path, "nextMsgButton.tooltip") }
+
+toolbar-compact-label = { COPY(from_path, "compactButton.label") }
+
+toolbar-compact =
+ .title = { COPY(from_path, "compactButton.tooltip") }
+
+toolbar-tag-message-label = { COPY(from_path, "tagButton.label") }
+
+toolbar-tag-message =
+ .title = { COPY(from_path, "tagButton.tooltip") }
+
+toolbar-forward-inline-label = { COPY(from_path, "forwardButton.label") }
+
+toolbar-forward-inline =
+ .title = { COPY(from_path, "forwardAsInline.tooltip") }
+
+toolbar-forward-attachment-label = { COPY(from_path, "buttonMenuForwardAsAttachment.label") }
+
+toolbar-forward-attachment =
+ .title = { COPY(from_path, "forwardAsAttachment.tooltip") }
+
+toolbar-mark-as-label = { COPY(from_path, "markButton.label") }
+
+toolbar-mark-as =
+ .title = { COPY(from_path, "markButton.tooltip") }
+
+toolbar-address-book-label = { COPY(from_path, "addressBookButton.title") }
+
+toolbar-address-book =
+ .title = { COPY(from_path, "addressBookButton.tooltip") }
+
+toolbar-chat-label = { COPY(from_path, "chatButton.label") }
+
+toolbar-chat =
+ .title = { COPY(from_path, "chatButton.tooltip") }
+
+toolbar-print-label = { COPY(from_path, "printButton.label") }
+
+toolbar-print =
+ .title = { COPY(from_path, "printButton.tooltip") }
+ """,
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-unifinder-label = { COPY(from_path, "showUnifinderCmd.label") }
+
+toolbar-unifinder =
+ .title = { COPY(from_path, "showUnifinderCmd.tooltip") }
+ """,
+ from_path="calendar/chrome/calendar/menuOverlay.dtd",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-edit-event-label = { COPY(from_path, "lightning.toolbar.edit.label") }
+
+toolbar-edit-event =
+ .title = { COPY(from_path, "lightning.toolbar.edit.tooltip") }
+
+toolbar-calendar-label = { COPY(from_path, "lightning.toolbar.calendar.label") }
+
+toolbar-calendar =
+ .title = { COPY(from_path, "lightning.toolbar.calendar.tooltip") }
+
+toolbar-tasks-label = { COPY(from_path, "lightning.toolbar.task.label") }
+
+toolbar-tasks =
+ .title = { COPY(from_path, "lightning.toolbar.task.tooltip") }
+ """,
+ from_path="calendar/chrome/lightning/lightning-toolbar.dtd",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-redirect-label = { COPY_PATTERN(from_path, "redirect-msg-button.label") }
+
+toolbar-redirect =
+ .title = { COPY_PATTERN(from_path, "redirect-msg-button.tooltiptext") }
+
+toolbar-add-ons-and-themes-label = { COPY_PATTERN(from_path, "addons-and-themes-toolbarbutton.label") }
+
+toolbar-add-ons-and-themes =
+ .title = { COPY_PATTERN(from_path, "addons-and-themes-toolbarbutton.tooltiptext") }
+
+
+toolbar-quick-filter-bar-label = { COPY_PATTERN(from_path, "quick-filter-toolbarbutton.label") }
+
+toolbar-quick-filter-bar =
+ .title = { COPY_PATTERN(from_path, "quick-filter-toolbarbutton.tooltiptext") }
+ """,
+ from_path="mail/messenger/messenger.ftl",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-junk-label = { COPY_PATTERN(from_path, "toolbar-junk-button.label") }
+
+toolbar-junk =
+ .title = { COPY_PATTERN(from_path, "toolbar-junk-button.tooltiptext") }
+
+toolbar-delete-label = { COPY_PATTERN(from_path, "toolbar-delete-button.label") }
+
+toolbar-delete =
+ .title = { COPY_PATTERN(from_path, "toolbar-delete-button.tooltiptext") }
+ """,
+ from_path="mail/messenger/menubar.ftl",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-add-as-event-label = { COPY(from_path, "calendar.extract.event.button") }
+
+toolbar-add-as-event =
+ .title = { COPY(from_path, "calendar.extract.event.button.tooltip") }
+
+toolbar-add-as-task-label = { COPY(from_path, "calendar.extract.task.button") }
+
+toolbar-add-as-task =
+ .title = { COPY(from_path, "calendar.extract.task.button.tooltip") }
+ """,
+ from_path="calendar/chrome/calendar/calendar.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1811400_thread_pane_column_picker.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1811400_thread_pane_column_picker.py
new file mode 100644
index 0000000000..925a4cbbb7
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1811400_thread_pane_column_picker.py
@@ -0,0 +1,79 @@
+# 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, transforms_from
+from fluent.migrate.transforms import REPLACE
+
+
+def migrate(ctx):
+ """Bug 1811400 - Migrate thread pane strings, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/about3Pane.ftl",
+ "mail/messenger/about3Pane.ftl",
+ transforms_from(
+ """
+apply-columns-to-menu =
+ .label = { COPY(from_path, "columnPicker.applyTo.label") }
+
+apply-current-view-to-folder =
+ .label = { COPY(from_path, "columnPicker.applyToFolder.label") }
+
+apply-current-view-to-folder-children =
+ .label = { COPY(from_path, "columnPicker.applyToFolderAndChildren.label") }
+ """,
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/about3Pane.ftl",
+ "mail/messenger/about3Pane.ftl",
+ transforms_from(
+ """
+apply-current-view-to-menu =
+ .label = { COPY_PATTERN(from_path, "apply-current-view-to-menu.label") }
+
+apply-changes-to-folder-title = {
+ COPY_PATTERN(from_path, "threadpane-apply-changes-prompt-title")
+}
+
+apply-current-view-to-folder-message = {
+ COPY_PATTERN(from_path, "threadpane-apply-changes-prompt-no-children-text")
+}
+
+apply-current-view-to-folder-with-children-message = {
+ COPY_PATTERN(from_path, "threadpane-apply-changes-prompt-with-children-text")
+}
+ """,
+ from_path="mail/messenger/mailWidgets.ftl",
+ ),
+ )
+ ctx.add_transforms(
+ "mail/messenger/about3Pane.ftl",
+ "mail/messenger/about3Pane.ftl",
+ [
+ FTL.Message(
+ id=FTL.Identifier("apply-current-columns-to-folder-message"),
+ value=REPLACE(
+ "mail/chrome/messenger/messenger.properties",
+ "threadPane.columnPicker.confirmFolder.noChildren.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("name"),
+ },
+ normalize_printf=True,
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("apply-current-columns-to-folder-with-children-message"),
+ value=REPLACE(
+ "mail/chrome/messenger/messenger.properties",
+ "threadPane.columnPicker.confirmFolder.withChildren.message",
+ {
+ "%1$S": VARIABLE_REFERENCE("name"),
+ },
+ normalize_printf=True,
+ ),
+ ),
+ ],
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1814393_unified_toolbar_customization_tabs.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1814393_unified_toolbar_customization_tabs.py
new file mode 100644
index 0000000000..5f4c3b469b
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1814393_unified_toolbar_customization_tabs.py
@@ -0,0 +1,36 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1814393 - Unified toolbar customization tab titles part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbar.ftl",
+ "mail/messenger/unifiedToolbar.ftl",
+ transforms_from(
+ """
+customize-space-tab-mail = { COPY_PATTERN(from_path, "customize-space-mail") }
+ .title = { COPY_PATTERN(from_path, "customize-space-mail") }
+
+customize-space-tab-addressbook = { COPY_PATTERN(from_path, "customize-space-addressbook") }
+ .title = { COPY_PATTERN(from_path, "customize-space-addressbook") }
+
+customize-space-tab-calendar = { COPY_PATTERN(from_path, "customize-space-calendar") }
+ .title = { COPY_PATTERN(from_path, "customize-space-calendar") }
+
+customize-space-tab-tasks = { COPY_PATTERN(from_path, "customize-space-tasks") }
+ .title = { COPY_PATTERN(from_path, "customize-space-tasks") }
+
+customize-space-tab-chat = { COPY_PATTERN(from_path, "customize-space-chat") }
+ .title = { COPY_PATTERN(from_path, "customize-space-chat") }
+
+customize-space-tab-settings = { COPY_PATTERN(from_path, "customize-space-settings") }
+ .title = { COPY_PATTERN(from_path, "customize-space-settings") }
+ """,
+ from_path="mail/messenger/unifiedToolbar.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1814664_unified_toolbar_calendar_items.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1814664_unified_toolbar_calendar_items.py
new file mode 100644
index 0000000000..5985124c42
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1814664_unified_toolbar_calendar_items.py
@@ -0,0 +1,48 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1814664 - Add calendar items to unified toolbar fluent migration part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-synchronize-label = { COPY(from_path, "lightning.toolbar.sync.label") }
+
+toolbar-synchronize =
+ .title = { COPY(from_path, "lightning.toolbar.sync.tooltip") }
+
+toolbar-delete-event-label = { COPY(from_path, "lightning.toolbar.delete.label") }
+
+toolbar-delete-event =
+ .title = { COPY(from_path, "lightning.toolbar.delete.tooltip") }
+
+toolbar-go-to-today-label = { COPY(from_path, "lightning.toolbar.gototoday.label") }
+
+toolbar-go-to-today =
+ .title = { COPY(from_path, "lightning.toolbar.gototoday.tooltip") }
+
+toolbar-print-event-label = { COPY(from_path, "lightning.toolbar.print.label") }
+
+toolbar-print-event =
+ .title = { COPY(from_path, "lightning.toolbar.print.tooltip") }
+
+toolbar-new-event-label = { COPY(from_path, "lightning.toolbar.newevent.label") }
+
+toolbar-new-event =
+ .title = { COPY(from_path, "lightning.toolbar.newevent.tooltip") }
+
+toolbar-new-task-label = { COPY(from_path, "lightning.toolbar.newtask.label") }
+
+toolbar-new-task =
+ .title = { COPY(from_path, "lightning.toolbar.newtask.tooltip") }
+ """,
+ from_path="calendar/chrome/lightning/lightning-toolbar.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1815489_add_back_forward_and_stop.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1815489_add_back_forward_and_stop.py
new file mode 100644
index 0000000000..8dab08d9ab
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1815489_add_back_forward_and_stop.py
@@ -0,0 +1,33 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb import COPY
+from fluent.migratetb.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1815489 - Add back, forward and stop to unified toolbar fluent migration part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-go-back-label = { COPY(from_path, "backButton1.label") }
+
+toolbar-go-back =
+ .title = { COPY(from_path, "goBackButton.tooltip") }
+
+toolbar-go-forward-label = { COPY(from_path, "goForwardButton1.label") }
+
+toolbar-go-forward =
+ .title = { COPY(from_path, "goForwardButton.tooltip") }
+
+toolbar-stop-label = { COPY(from_path, "stopButton.label") }
+
+toolbar-stop =
+ .title = { COPY(from_path, "stopButton.tooltip") }
+ """,
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1815605_calendar_context_menu_has_empty_items.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1815605_calendar_context_menu_has_empty_items.py
new file mode 100644
index 0000000000..de0157ac47
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1815605_calendar_context_menu_has_empty_items.py
@@ -0,0 +1,52 @@
+# 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 1815605 - calendar context menu has empty items, part {index}."""
+
+ ctx.add_transforms(
+ "calendar/calendar/calendar-widgets.ftl",
+ "calendar/calendar/calendar-widgets.ftl",
+ transforms_from(
+ """
+calendar-context-menu-previous-day =
+ .label = { COPY(from_path, "calendar.prevday.label") }
+ .accesskey = { COPY(from_path, "calendar.prevday.accesskey") }
+
+calendar-context-menu-previous-week =
+ .label = { COPY(from_path, "calendar.prevweek.label") }
+ .accesskey = { COPY(from_path, "calendar.prevweek.accesskey") }
+
+calendar-context-menu-previous-multiweek =
+ .label = { COPY(from_path, "calendar.prevweek.label") }
+ .accesskey = { COPY(from_path, "calendar.prevweek.accesskey") }
+
+calendar-context-menu-previous-month =
+ .label = { COPY(from_path, "calendar.prevmonth.label") }
+ .accesskey = { COPY(from_path, "calendar.prevmonth.accesskey") }
+
+calendar-context-menu-next-day =
+ .label = { COPY(from_path, "calendar.nextday.label") }
+ .accesskey = { COPY(from_path, "calendar.nextday.accesskey") }
+
+calendar-context-menu-next-week =
+ .label = { COPY(from_path, "calendar.nextweek.label") }
+ .accesskey = { COPY(from_path, "calendar.nextweek.accesskey") }
+
+calendar-context-menu-next-multiweek =
+ .label = { COPY(from_path, "calendar.nextweek.label") }
+ .accesskey = { COPY(from_path, "calendar.nextweek.accesskey") }
+
+calendar-context-menu-next-month =
+ .label = { COPY(from_path, "calendar.nextmonth.label") }
+ .accesskey = { COPY(from_path, "calendar.nextmonth.accesskey") }
+""",
+ from_path="calendar/chrome/calendar/calendar.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1816532_about_dialog_migration.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1816532_about_dialog_migration.py
new file mode 100644
index 0000000000..cc47261099
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1816532_about_dialog_migration.py
@@ -0,0 +1,94 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+
+from fluent.migratetb.helpers import TERM_REFERENCE
+from fluent.migratetb.helpers import transforms_from
+
+
+# This can't just be a straight up literal dict (eg: {"a":"b"}) because the
+# validator fails... so make it a function call that returns a dict.. it works
+about_replacements = dict(
+ {
+ "&brandShorterName;": TERM_REFERENCE("brand-shorter-name"),
+ "&brandShortName;": TERM_REFERENCE("brand-short-name"),
+ "&vendorShortName;": TERM_REFERENCE("vendor-short-name"),
+ }
+)
+
+
+def migrate(ctx):
+ """Bug 1816532 - Migrate aboutDialog.dtd strings to Fluent, part {index}"""
+ target = reference = "mail/messenger/aboutDialog.ftl"
+ source = "mail/chrome/messenger/aboutDialog.dtd"
+
+ ctx.add_transforms(
+ target,
+ reference,
+ transforms_from(
+ """
+release-notes-link = { COPY(source, "releaseNotes.link") }
+
+update-check-for-updates-button = { COPY(source, "update.checkForUpdatesButton.label") }
+ .accesskey = { COPY(source, "update.checkForUpdatesButton.accesskey") }
+
+update-update-button = { REPLACE(source, "update.updateButton.label3", about_replacements) }
+ .accesskey = { COPY(source, "update.updateButton.accesskey") }
+
+update-checking-for-updates = { COPY(source, "update.checkingForUpdates") }
+
+update-downloading-message = { COPY(source, "update.downloading.start") }<span data-l10n-name="download-status"></span>
+
+update-applying = { COPY(source, "update.applying") }
+
+update-downloading = <img data-l10n-name="icon"/>{ COPY(source, "update.downloading.start") }<span data-l10n-name="download-status"></hspan>
+
+update-failed = { COPY(source, "update.failed.start") }<a data-l10n-name="failed-link">{ COPY(source, "update.failed.linkText") }</a>
+
+update-admin-disabled = { COPY(source, "update.adminDisabled") }
+
+update-no-updates-found = { REPLACE(source, "update.noUpdatesFound", about_replacements) }
+
+update-other-instance-handling-updates = { REPLACE(source, "update.otherInstanceHandlingUpdates", about_replacements) }
+
+update-unsupported = { COPY(source, "update.unsupported.start") }<a data-l10n-name="unsupported-link">{ COPY(source, "update.unsupported.linkText") }</a>
+
+update-restarting = { COPY(source, "update.restarting") }
+
+channel-description = { COPY(source, "channel.description.start") }<span data-l10n-name="current-channel">{ $channel }</span> { COPY(source, "channel.description.end", trim: "True") }
+
+warning-desc-version = { REPLACE(source, "warningDesc.version", about_replacements) }
+
+warning-desc-telemetry = { REPLACE(source, "warningDesc.telemetryDesc", about_replacements) }
+
+community-exp = <a data-l10n-name="community-exp-mozilla-link">
+ { REPLACE(source, "community.exp.mozillaLink", about_replacements) }</a>
+ { COPY(source, "community.exp.middle") }<a data-l10n-name="community-exp-credits-link">
+ { COPY(source, "community.exp.creditsLink") }</a>
+ { COPY(source, "community.exp.end") }
+
+community-2 = { REPLACE(source, "community.start2", about_replacements) }<a data-l10n-name="community-mozilla-link">
+ { REPLACE(source, "community.mozillaLink", about_replacements) }</a>
+ { COPY(source, "community.middle2") }<a data-l10n-name="community-credits-link">
+ { COPY(source, "community.creditsLink") }</a>
+ { COPY(source, "community.end3") }
+
+about-helpus = { COPY(source, "helpus.start") }<a data-l10n-name="helpus-donate-link">
+ { COPY(source, "helpus.donateLink") }</a> or <a data-l10n-name="helpus-get-involved-link">
+ { COPY(source, "helpus.getInvolvedLink") }</a>
+
+bottom-links-license = { COPY(source, "bottomLinks.license") }
+
+bottom-links-rights = { COPY(source, "bottomLinks.rights") }
+
+bottom-links-privacy = { COPY(source, "bottomLinks.privacy") }
+
+cmd-close-mac-command-key =
+ .key = { COPY(source, "cmdCloseMac.commandKey") }
+""",
+ source=source,
+ about_replacements=about_replacements,
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1816593_flexbox_emulation_dialogs.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1816593_flexbox_emulation_dialogs.py
new file mode 100644
index 0000000000..a920b9c0fd
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1816593_flexbox_emulation_dialogs.py
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migrate import COPY_PATTERN
+from fluent.migrate.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1816593 - Fix wrong sized dialogs due to flexbox emulation, part {index}"""
+
+ ctx.add_transforms(
+ "mail/messenger/compactFoldersDialog.ftl",
+ "mail/messenger/compactFoldersDialog.ftl",
+ transforms_from(
+ """
+compact-dialog-window-title =
+ .title = {{COPY_PATTERN(from_path, "compact-dialog-window.title")}}
+ """,
+ from_path="mail/messenger/compactFoldersDialog.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1817914_tags_mode.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1817914_tags_mode.py
new file mode 100644
index 0000000000..d8b3c80d0c
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1817914_tags_mode.py
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1820700 - Implement a Tags folder pane mode, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/messenger.ftl",
+ "mail/messenger/messenger.ftl",
+ transforms_from(
+ """
+show-tags-folders-label =
+ .label = {COPY(from_path, "viewTags.label")}
+ .accesskey = {COPY(from_path, "viewTags.accesskey")}
+""",
+ from_path="mail/chrome/messenger/msgViewPickerOverlay.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1817915_get_new_messages.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1817915_get_new_messages.py
new file mode 100644
index 0000000000..2e2c1ac497
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1817915_get_new_messages.py
@@ -0,0 +1,22 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb.helpers import transforms_from
+from fluent.migratetb import COPY
+
+
+def migrate(ctx):
+ """Bug 1817915 - Add context menu to Get Messages folder pane button to fetch all messages or per account, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/about3Pane.ftl",
+ "mail/messenger/about3Pane.ftl",
+ transforms_from(
+ """
+folder-pane-get-all-messages-menuitem =
+ .label = { COPY(from_path, "getAllNewMsgCmd.label") }
+ .accesskey = { COPY(from_path, "getAllNewMsgCmd.accesskey") }
+ """,
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1820700_select_thread.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1820700_select_thread.py
new file mode 100644
index 0000000000..ae65f694d1
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1820700_select_thread.py
@@ -0,0 +1,35 @@
+# 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 https://mozilla.org/MPL/2.0/.
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migratetb.helpers import VARIABLE_REFERENCE, transforms_from
+from fluent.migratetb.transforms import REPLACE
+
+
+def migrate(ctx):
+ """Bug 1820700 - Migrate thread img strings to button, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/treeView.ftl",
+ "mail/messenger/treeView.ftl",
+ transforms_from(
+ """
+tree-list-view-row-thread-button =
+ .title = {COPY_PATTERN(from_path, "tree-list-view-row-thread-icon.title")}
+
+tree-list-view-row-ignored-thread-button =
+ .title = {COPY_PATTERN(from_path, "tree-list-view-row-ignored-thread-icon.title")}
+
+tree-list-view-row-ignored-subthread-button =
+ .title = {COPY_PATTERN(from_path, "tree-list-view-row-ignored-subthread-icon.title")}
+
+tree-list-view-row-watched-thread-button =
+ .title = {COPY_PATTERN(from_path, "tree-list-view-row-watched-thread-icon.title")}
+""",
+ from_path="mail/messenger/treeView.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1823033_activity_indicator.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1823033_activity_indicator.py
new file mode 100644
index 0000000000..4cfaec4695
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1823033_activity_indicator.py
@@ -0,0 +1,23 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb import COPY
+from fluent.migratetb.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1823033 - Add activity indicator to unified toolbar fluent migration part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/unifiedToolbarItems.ftl",
+ "mail/messenger/unifiedToolbarItems.ftl",
+ transforms_from(
+ """
+toolbar-throbber-label = { COPY(from_path, "throbberItem.title") }
+
+toolbar-throbber =
+ .title = { COPY(from_path, "throbberItem.title") }
+ """,
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1827261_add_enable_disable_compact_mode_options_to_folder_mode_context_menu.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1827261_add_enable_disable_compact_mode_options_to_folder_mode_context_menu.py
new file mode 100644
index 0000000000..7ea090e903
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1827261_add_enable_disable_compact_mode_options_to_folder_mode_context_menu.py
@@ -0,0 +1,26 @@
+# 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 https://mozilla.org/MPL/2.0/.
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb.helpers import transforms_from
+from fluent.migratetb import COPY
+
+
+def migrate(ctx):
+ """Bug 1827261 - Add enable/disable compact mode options to Folder Mode context menu, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/about3Pane.ftl",
+ "mail/messenger/about3Pane.ftl",
+ transforms_from(
+ """
+folder-pane-mode-context-toggle-compact-mode =
+ .label = { COPY(from_path, "compactVersion.label") }
+ .accesskey = { COPY(from_path, "compactVersion.accesskey") }
+ """,
+ from_path="mail/chrome/messenger/messenger.dtd",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1827891_dnt_prefs_learn_more.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1827891_dnt_prefs_learn_more.py
new file mode 100644
index 0000000000..4861e428bc
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1827891_dnt_prefs_learn_more.py
@@ -0,0 +1,21 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from fluent.migratetb import COPY_PATTERN
+from fluent.migratetb.helpers import transforms_from
+
+
+def migrate(ctx):
+ """Bug 1827891 - DNT Prefs 'learn more' link fix, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/preferences/preferences.ftl",
+ "mail/messenger/preferences/preferences.ftl",
+ transforms_from(
+ """
+dnt-learn-more-button =
+ .value = {{ COPY_PATTERN(from_path, "learn-button.label") }}
+ """,
+ from_path="mail/messenger/preferences/preferences.ftl",
+ ),
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1828340_aboutdialog_layout_fixes.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1828340_aboutdialog_layout_fixes.py
new file mode 100644
index 0000000000..d162e94659
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1828340_aboutdialog_layout_fixes.py
@@ -0,0 +1,42 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import re
+
+from fluent.migratetb import COPY_PATTERN
+from fluent.migratetb.transforms import TransformPattern
+
+import fluent.syntax.ast as FTL
+
+
+class STRIP_NEWLINES(TransformPattern):
+ def visit_TextElement(self, node):
+ node.value = re.sub("\n", "", node.value)
+ return node
+
+
+def migrate(ctx):
+ """Bug 1828340 - Fix aboutDialog layout issues, part {index}."""
+ path = "mail/messenger/aboutDialog.ftl"
+ ctx.add_transforms(
+ path,
+ path,
+ [
+ FTL.Message(
+ id=FTL.Identifier("about-dialog-title"),
+ value=COPY_PATTERN(path, "aboutDialog-title.title"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("community-experimental"),
+ value=STRIP_NEWLINES(path, "community-exp"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("community-desc"),
+ value=STRIP_NEWLINES(path, "community-2"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("about-donation"),
+ value=STRIP_NEWLINES(path, "about-helpus"),
+ ),
+ ],
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1830004_folder_quota.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1830004_folder_quota.py
new file mode 100644
index 0000000000..07f4505912
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1830004_folder_quota.py
@@ -0,0 +1,28 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migratetb.helpers import VARIABLE_REFERENCE, transforms_from
+from fluent.migratetb.transforms import REPLACE
+
+
+def migrate(ctx):
+ """Bug 1830004 - Migrate a quota string, part {index}."""
+
+ ctx.add_transforms(
+ "mail/messenger/folderprops.ftl",
+ "mail/messenger/folderprops.ftl",
+ [
+ FTL.Message(
+ id=FTL.Identifier("quota-percent-used"),
+ value=REPLACE(
+ "mail/chrome/messenger/messenger.properties",
+ "quotaPercentUsed",
+ {
+ "%1$S": VARIABLE_REFERENCE("percent"),
+ },
+ normalize_printf=True,
+ ),
+ ),
+ ],
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_cal_enable78.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_cal_enable78.py
new file mode 100644
index 0000000000..b3f6c420c6
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_cal_enable78.py
@@ -0,0 +1,30 @@
+# 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 https://mozilla.org/MPL/2.0/.
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migratetb.helpers import VARIABLE_REFERENCE, transforms_from
+from fluent.migratetb.transforms import REPLACE
+from fluent.migratetb.transforms import COPY
+
+
+def migrate(ctx):
+ """Bug 1834662 - Migrate calendar enable button, part {index}."""
+ target = reference = "calendar/calendar/calendar-widgets.ftl"
+
+ ctx.add_transforms(
+ target,
+ reference,
+ [
+ FTL.Message(
+ id=FTL.Identifier("calendar-enable-button"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.sideloadEnable.label",
+ ),
+ ),
+ ],
+ )
diff --git a/comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_extensions_to_fluent.py b/comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_extensions_to_fluent.py
new file mode 100644
index 0000000000..cdd8736b67
--- /dev/null
+++ b/comm/python/l10n/tb_fluent_migrations/completed/bug_1834662_extensions_to_fluent.py
@@ -0,0 +1,531 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import fluent.syntax.ast as FTL
+from fluent.migratetb.helpers import TERM_REFERENCE, VARIABLE_REFERENCE
+from fluent.migratetb.transforms import (
+ COPY,
+ COPY_PATTERN,
+ PLURALS,
+ REPLACE,
+ REPLACE_IN_TEXT,
+)
+
+
+def migrate(ctx):
+ """Bug 1834662 - Migrate addon/extension stuff, part {index}."""
+
+ # extensionPermissions.ftl - from addons.properties
+ ctx.add_transforms(
+ "mail/messenger/extensionPermissions.ftl",
+ "mail/messenger/extensionPermissions.ftl",
+ [
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-accountsFolders"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.accountsFolders",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-accountsIdentities"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.accountsIdentities",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-accountsRead"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.accountsRead2",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-addressBooks"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.addressBooks",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-compose"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.compose",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-compose"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.compose",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-compose-send"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.compose.send",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-compose-save"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.compose.save",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-experiment"),
+ value=REPLACE(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.experiment",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-messagesImport"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.messagesImport",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-messagesModify"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.messagesModify",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-messagesMove"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.messagesMove2",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-messagesDelete"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.messagesDelete",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-messagesRead"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.messagesRead",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-messagesTags"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.messagesTags",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-description-sensitiveDataUpload"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.description.sensitiveDataUpload",
+ ),
+ ),
+ ],
+ )
+
+ # extensionsUI.ftl - from here and there
+ ctx.add_transforms(
+ "mail/messenger/extensionsUI.ftl",
+ "mail/messenger/extensionsUI.ftl",
+ [
+ FTL.Message(
+ id=FTL.Identifier("webext-experiment-warning"),
+ value=COPY(
+ "mail/chrome/messenger/addons.properties",
+ "webextPerms.experimentWarning",
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-learn-more"),
+ value=COPY("mail/chrome/messenger/addons.properties", "webextPerms.learnMore2"),
+ ),
+ ],
+ )
+
+ # addonNotifications.ftl - copied from browser/ migration script
+
+ addons_properties = "mail/chrome/messenger/addons.properties"
+ notifications = "mail/messenger/addonNotifications.ftl"
+
+ ctx.add_transforms(
+ notifications,
+ notifications,
+ [
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt"),
+ value=REPLACE(
+ addons_properties,
+ "xpinstallPromptMessage",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-header"),
+ value=REPLACE(
+ addons_properties,
+ "xpinstallPromptMessage.header",
+ {"%1$S": VARIABLE_REFERENCE("host")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-message"),
+ value=REPLACE(
+ addons_properties,
+ "xpinstallPromptMessage.message",
+ {"%1$S": VARIABLE_REFERENCE("host")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-header-unknown"),
+ value=COPY(addons_properties, "xpinstallPromptMessage.header.unknown"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-message-unknown"),
+ value=COPY(addons_properties, "xpinstallPromptMessage.message.unknown"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-dont-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(addons_properties, "xpinstallPromptMessage.dontAllow"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ addons_properties,
+ "xpinstallPromptMessage.dontAllow.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-never-allow"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(addons_properties, "xpinstallPromptMessage.neverAllow"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ addons_properties,
+ "xpinstallPromptMessage.neverAllow.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-never-allow-and-report"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(
+ addons_properties,
+ "xpinstallPromptMessage.neverAllowAndReport",
+ ),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ addons_properties,
+ "xpinstallPromptMessage.neverAllowAndReport.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("site-permission-install-first-prompt-midi-header"),
+ value=COPY(addons_properties, "sitePermissionInstallFirstPrompt.midi.header"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("site-permission-install-first-prompt-midi-message"),
+ value=COPY(addons_properties, "sitePermissionInstallFirstPrompt.midi.message"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-prompt-install"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(addons_properties, "xpinstallPromptMessage.install"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(
+ addons_properties,
+ "xpinstallPromptMessage.install.accesskey",
+ ),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-disabled-locked"),
+ value=COPY(addons_properties, "xpinstallDisabledMessageLocked"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-disabled"),
+ value=COPY(addons_properties, "xpinstallDisabledMessage"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("xpinstall-disabled-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(addons_properties, "xpinstallDisabledButton"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(addons_properties, "xpinstallDisabledButton.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-blocked-by-policy"),
+ value=REPLACE(
+ addons_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(addons_properties, "addonDomainBlockedByPolicy"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-full-screen-blocked"),
+ value=COPY(addons_properties, "addonInstallFullScreenBlocked"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("webext-perms-sideload-menu-item"),
+ value=REPLACE(
+ addons_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(
+ addons_properties,
+ "webextPerms.updateMenuItem",
+ {"%1$S": VARIABLE_REFERENCE("addonName")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-removal-message"),
+ value=REPLACE(
+ addons_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(addons_properties, "webext.remove.confirmation.button"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-downloading-and-verifying"),
+ value=PLURALS(
+ addons_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(addons_properties, "addonDownloadVerifying"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-cancel-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(addons_properties, "addonInstall.cancelButton.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(addons_properties, "addonInstall.cancelButton.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-accept-button"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY(addons_properties, "addonInstall.acceptButton2.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY(addons_properties, "addonInstall.acceptButton2.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-confirm-install-message"),
+ value=PLURALS(
+ addons_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(
+ addons_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(
+ addons_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(addons_properties, "addonInstallError-1"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-incorrect-hash"),
+ value=REPLACE(
+ addons_properties,
+ "addonInstallError-2",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-corrupt-file"),
+ value=COPY(addons_properties, "addonInstallError-3"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-file-access"),
+ value=REPLACE(
+ addons_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(
+ addons_properties,
+ "addonInstallError-5",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-invalid-domain"),
+ value=REPLACE(
+ addons_properties,
+ "addonInstallError-8",
+ {"%2$S": VARIABLE_REFERENCE("addonName")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-network-failure"),
+ value=COPY(addons_properties, "addonLocalInstallError-1"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-incorrect-hash"),
+ value=REPLACE(
+ addons_properties,
+ "addonLocalInstallError-2",
+ {"%1$S": TERM_REFERENCE("brand-short-name")},
+ ),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-corrupt-file"),
+ value=COPY(addons_properties, "addonLocalInstallError-3"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-local-install-error-file-access"),
+ value=REPLACE(
+ addons_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(addons_properties, "addonLocalInstallError-5"),
+ ),
+ FTL.Message(
+ id=FTL.Identifier("addon-install-error-incompatible"),
+ value=REPLACE(
+ addons_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(
+ addons_properties,
+ "addonInstallErrorBlocklisted",
+ {"%1$S": VARIABLE_REFERENCE("addonName")},
+ ),
+ ),
+ ],
+ )
diff --git a/comm/python/l10n/tbxchannel/__init__.py b/comm/python/l10n/tbxchannel/__init__.py
new file mode 100644
index 0000000000..b6599032bc
--- /dev/null
+++ b/comm/python/l10n/tbxchannel/__init__.py
@@ -0,0 +1,47 @@
+# 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/.
+
+from pathlib import Path
+
+from .l10n_merge import COMM_STRINGS_QUARANTINE, COMM_STRINGS_QUARANTINE_PUSH
+
+TB_XC_NOTIFICATION_TMPL = """\
+**Thunderbird L10n Cross Channel**
+
+Changes pushed to `comm-strings-quarantine`: {rev_url}
+"""
+
+
+def get_thunderbird_xc_config(topsrcdir, strings_path):
+ assert isinstance(topsrcdir, Path)
+ assert isinstance(strings_path, Path)
+ return {
+ "strings": {
+ "path": strings_path,
+ "url": COMM_STRINGS_QUARANTINE,
+ "heads": {"default": "default"},
+ "update_on_pull": True,
+ "push_url": COMM_STRINGS_QUARANTINE_PUSH,
+ },
+ "source": {
+ "comm-central": {
+ "path": topsrcdir / "comm",
+ "url": "https://hg.mozilla.org/comm-central/",
+ "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. esr102 goes after esr115).
+ "comm": "comm-central",
+ "comm-beta": "releases/comm-beta",
+ "comm-esr102": "releases/comm-esr102",
+ },
+ "config_files": [
+ "comm/calendar/locales/l10n.toml",
+ "comm/mail/locales/l10n.toml",
+ "comm/suite/locales/l10n.toml",
+ ],
+ },
+ },
+ }
diff --git a/comm/python/l10n/tbxchannel/l10n_merge.py b/comm/python/l10n/tbxchannel/l10n_merge.py
new file mode 100644
index 0000000000..b202e7dfe0
--- /dev/null
+++ b/comm/python/l10n/tbxchannel/l10n_merge.py
@@ -0,0 +1,26 @@
+# 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/.
+
+L10N_CENTRAL = "https://hg.mozilla.org/l10n-central"
+COMM_L10N = "https://hg.mozilla.org/projects/comm-l10n"
+COMM_L10N_PUSH = f"ssh{COMM_L10N[5:]}"
+COMM_STRINGS_QUARANTINE = "https://hg.mozilla.org/projects/comm-strings-quarantine"
+COMM_STRINGS_QUARANTINE_PUSH = f"ssh{COMM_STRINGS_QUARANTINE[5:]}"
+
+
+GECKO_STRINGS_PATTERNS = (
+ "{lang}/browser/pdfviewer/**",
+ "{lang}/devtools/**",
+ "{lang}/dom/**",
+ "{lang}/extensions/spellcheck/**",
+ "{lang}/netwerk/**",
+ "{lang}/security/**",
+ "{lang}/toolkit/**",
+)
+
+COMM_STRINGS_PATTERNS = (
+ "{lang}/calendar/**",
+ "{lang}/chat/**",
+ "{lang}/mail/**",
+)
diff --git a/comm/python/l10n/tbxchannel/quarantine_to_strings.py b/comm/python/l10n/tbxchannel/quarantine_to_strings.py
new file mode 100644
index 0000000000..def3f59e5e
--- /dev/null
+++ b/comm/python/l10n/tbxchannel/quarantine_to_strings.py
@@ -0,0 +1,194 @@
+# 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 https://mozilla.org/MPL/2.0/.
+
+import logging
+import os
+import shutil
+import subprocess
+import tempfile
+from pathlib import Path
+
+from typing_extensions import Literal
+
+from mozversioncontrol import HgRepository
+from mozversioncontrol.repoupdate import update_mercurial_repo
+
+from .l10n_merge import COMM_L10N, COMM_L10N_PUSH, COMM_STRINGS_QUARANTINE
+
+ACTIONS = Literal["clean", "prep", "migrate", "push"]
+
+
+class HgL10nRepository(HgRepository):
+ log_trans_table = str.maketrans({"{": "{{", "}": "}}"})
+
+ def __init__(self, path: Path, check_url=None, logger=print):
+ super(HgL10nRepository, self).__init__(path, hg="hg")
+ self._logger = logger
+ if check_url is not None:
+ self._check_hg_url(check_url)
+
+ def logger(self, *args):
+ # Escape python-style format string substitutions because Sentry is annoying
+ self._logger(*args[:-1], args[-1].translate(self.log_trans_table))
+
+ def _check_hg_url(self, repo_url):
+ configured_url = self._run("config", "paths.default").strip()
+ if configured_url != repo_url:
+ raise Exception(f"Repository does not match {repo_url}.")
+
+ def check_status(self):
+ if not self.working_directory_clean() or self.get_outgoing_files():
+ raise Exception(f"Repository at {self.path} is not clean, run with 'clean'.")
+
+ def last_convert_rev(self):
+ args = (
+ "log",
+ "-r",
+ "last(extra('convert_source', 'comm-strings-quarantine'))",
+ "--template",
+ "{get(extras,'convert_revision')}\n",
+ )
+ self.logger(logging.INFO, "last_convert_rev", {}, " ".join(args))
+ rv = self._run(*args).strip()
+ self.logger(logging.INFO, "last_convert_rev", {}, rv)
+ return rv
+
+ def next_convert_rev(self, last_converted):
+ args = ("log", "-r", f"first(children({last_converted}))", "--template", "{node}\n")
+ self.logger(logging.INFO, "next_convert_rev", {}, " ".join(args))
+ rv = self._run(*args).strip()
+ self.logger(logging.INFO, "next_convert_rev", {}, rv)
+ return rv
+
+ def convert_quarantine(self, strings_path, filemap_path, splicemap_path, next_converted_rev):
+ args = (
+ "convert",
+ "--config",
+ "convert.hg.saverev=True",
+ "--config",
+ "convert.hg.sourcename=comm-strings-quarantine",
+ "--config",
+ f"convert.hg.revs={next_converted_rev}:tip",
+ "--filemap",
+ filemap_path,
+ "--splicemap",
+ splicemap_path,
+ "--datesort",
+ str(self.path),
+ str(strings_path.absolute()),
+ )
+ self.logger(logging.INFO, "convert_quarantine", {}, " ".join(args))
+ rv = self._run(*args)
+ self.logger(logging.INFO, "convert_quarantine", {}, rv)
+ return rv
+
+ def push(self, push_url):
+ popen_kwargs = {
+ "stdout": subprocess.PIPE,
+ "stderr": subprocess.PIPE,
+ "cwd": self.path,
+ "env": self._env,
+ "universal_newlines": True,
+ "bufsize": 1,
+ }
+ cmd = ("hg", "push", "-r", ".", push_url)
+ self.logger(logging.INFO, "push", {}, " ".join(cmd))
+ # This function doesn't really push to try...
+ self._push_to_try_with_log_capture(cmd, popen_kwargs)
+
+
+def _nuke_hg_repos(*paths: Path):
+ failed = {}
+ for path in paths:
+ try:
+ if path.exists():
+ shutil.rmtree(str(path))
+ except Exception as e:
+ failed[str(path)] = e
+
+ if failed:
+ for f in failed:
+ print(f"Unable to nuke '{f}': {failed[f]}")
+ raise Exception()
+
+
+def publish_strings(
+ command_context,
+ quarantine_path: Path,
+ comm_l10n_path: Path,
+ actions: ACTIONS,
+ **kwargs,
+):
+ if "clean" in actions:
+ command_context.log(logging.INFO, "clean", {}, "Removing old repository clones.")
+ _nuke_hg_repos(quarantine_path, comm_l10n_path)
+
+ if "prep" in actions:
+ # update_mercurial_repo also will clone if a repo is not already there
+ command_context.log(
+ logging.INFO, "prep", {}, f"Updating comm-strings-quarantine at {quarantine_path}."
+ )
+ update_mercurial_repo("hg", COMM_STRINGS_QUARANTINE, quarantine_path)
+ command_context.log(logging.INFO, "prep", {}, f"Updating comm-l10n at {comm_l10n_path}.")
+ update_mercurial_repo("hg", COMM_L10N, comm_l10n_path)
+
+ local_quarantine = HgL10nRepository(
+ quarantine_path, COMM_STRINGS_QUARANTINE, command_context.log
+ )
+ local_comm_l10n = HgL10nRepository(comm_l10n_path, COMM_L10N, command_context.log)
+
+ if "prep" not in actions:
+ local_quarantine.update("tip")
+ local_comm_l10n.update("tip")
+
+ if "migrate" in actions:
+ local_quarantine.check_status()
+ local_comm_l10n.check_status()
+
+ command_context.log(
+ logging.INFO, "migrate", {}, "Starting string migration from quarantine."
+ )
+ head_rev = local_comm_l10n.head_ref
+ last_convert_rev = local_comm_l10n.last_convert_rev()
+ first_convert_rev = local_quarantine.next_convert_rev(last_convert_rev)
+ command_context.log(
+ logging.INFO, "migrate", {}, f" Last converted rev: {last_convert_rev}"
+ )
+ command_context.log(
+ logging.INFO, "migrate", {}, f" First converted rev: {first_convert_rev}"
+ )
+
+ with tempfile.NamedTemporaryFile(
+ prefix="splicemap", suffix=".txt", delete=False
+ ) as splice_fp:
+ splicemap = splice_fp.name
+ command_context.log(
+ logging.INFO, "migrate", {}, f" Writing splicemap to: {splicemap}"
+ )
+ splice_fp.write(f"{first_convert_rev} {head_rev}\n".encode("utf-8"))
+
+ with tempfile.NamedTemporaryFile(prefix="filemap", suffix=".txt", delete=False) as file_fp:
+ filemap = file_fp.name
+ command_context.log(logging.INFO, "migrate", {}, f" Writing filemap to: {filemap}")
+ file_fp.writelines(
+ ["exclude _configs\n".encode("utf-8"), "rename . en-US\n".encode("utf-8")]
+ )
+
+ command_context.log(logging.INFO, "migrate", {}, " Running hg convert...")
+ local_quarantine.convert_quarantine(comm_l10n_path, filemap, splicemap, first_convert_rev)
+ try:
+ os.unlink(splicemap)
+ os.unlink(filemap)
+ except Exception:
+ pass
+
+ local_comm_l10n.update("tip")
+ command_context.log(logging.INFO, "migrate", {}, " Finished!")
+
+ if "push" in actions:
+ if local_comm_l10n.get_outgoing_files():
+ command_context.log(logging.INFO, "push", {}, " Pushing to comm-l10n.")
+ local_comm_l10n.push(COMM_L10N_PUSH)
+ else:
+ command_context.log(logging.INFO, "push", {}, "Skipping empty push.")
diff --git a/comm/python/l10n/tbxchannel/tb_migration_test.py b/comm/python/l10n/tbxchannel/tb_migration_test.py
new file mode 100644
index 0000000000..8e01e1d889
--- /dev/null
+++ b/comm/python/l10n/tbxchannel/tb_migration_test.py
@@ -0,0 +1,172 @@
+# 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/.
+"""
+Test comm-l10n Fluent migrations
+"""
+
+import logging
+import os
+import re
+import shutil
+
+import hglib
+from compare_locales.merge import merge_channels
+from compare_locales.paths.configparser import TOMLParser
+from compare_locales.paths.files import ProjectFiles
+from fluent.migratetb import validator
+from test_fluent_migrations.fmt import diff_resources
+
+import mozpack.path as mozpath
+from mach.util import get_state_dir
+from mozversioncontrol.repoupdate import update_mercurial_repo
+
+from .l10n_merge import COMM_L10N
+
+
+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 comm-l10n.
+
+ We run this once per mach invocation, for all tested migrations.
+ """
+ obj_dir = mozpath.join(cmd.topobjdir, "comm", "python", "l10n")
+ if not os.path.exists(obj_dir):
+ os.makedirs(obj_dir)
+ state_dir = get_state_dir()
+ update_mercurial_repo("hg", COMM_L10N, mozpath.join(state_dir, "comm-strings"))
+ return obj_dir
+
+
+def test_migration(cmd, obj_dir, to_test, references):
+ """Test the given recipe.
+
+ This creates a workdir by merging comm-strings-quarantine and the c-c source,
+ to mimic comm-strings-quarantine after the patch to test landed.
+ It then runs the recipe with a comm-strings-quarantine 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]
+ l10n_lib = os.path.abspath(os.path.dirname(os.path.dirname(to_test)))
+ 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,
+ "tb-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(f"Bad reference path: {ref} - {full_ref}")
+ m_c_path = m[1]
+ g_s_path = mozpath.join(work_dir, "comm-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(), "comm-strings"),
+ dest=mozpath.join(work_dir, "comm-strings"),
+ )
+ client.open()
+ old_tip = client.tip().node
+ run_migration = [
+ cmd._virtualenv_manager.python_path,
+ "-m",
+ "fluent.migratetb.tool",
+ "--locale",
+ "en-US",
+ "--reference-dir",
+ mozpath.join(work_dir, "reference"),
+ "--localization-dir",
+ mozpath.join(work_dir, "comm-strings"),
+ "--dry-run",
+ migration_module,
+ ]
+ append_env = {"PYTHONPATH": l10n_lib}
+ cmd.run_process(
+ run_migration,
+ append_env=append_env,
+ cwd=work_dir,
+ line_handler=print,
+ )
+ # drop --dry-run
+ run_migration.pop(-2)
+ cmd.run_process(
+ run_migration,
+ append_env=append_env,
+ cwd=work_dir,
+ line_handler=print,
+ )
+ tip = client.tip().node
+ if old_tip == tip:
+ cmd.log(
+ logging.WARN,
+ "tb-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, "comm-strings", "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,
+ "tb-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,
+ "tb-fluent-migration-test",
+ {
+ "file": to_test,
+ },
+ 'Commit messages should have "part {{index}}" for {file}',
+ )
+ return rv