diff options
Diffstat (limited to 'python/l10n')
21 files changed, 1352 insertions, 0 deletions
diff --git a/python/l10n/convert_xul_to_fluent/convert.py b/python/l10n/convert_xul_to_fluent/convert.py new file mode 100644 index 0000000000..5185b3d5de --- /dev/null +++ b/python/l10n/convert_xul_to_fluent/convert.py @@ -0,0 +1,118 @@ +# 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 __future__ import absolute_import +from __future__ import print_function +from lib.xul import collect_messages +from lib.dtd import get_dtds +from lib.utils import read_file, write_file +from lib.migration import build_migration +from lib.fluent import build_ftl +import json +import argparse +import sys + +# import re + +# To run this, you'll need to have lxml installed: +# `pip install lxml` + +# default migration directions +data = { + "migration": "python/l10n/fluent_migrations", + "description": "Migrate l10n strings", + "prefix": "", +} + + +def parse_inputs(): + sys_args = sys.argv[1:] + + parser = argparse.ArgumentParser() + parser.add_argument("bug_id", type=str, help="Id number for bug tracking") + parser.add_argument( + "xul", type=str, help="POSIX path from mozilla-central to the XML to be updated" + ) + parser.add_argument( + "ftl", + type=str, + help="Case sensitive POSIX path from mozilla-central to desired ftl file", + ) + parser.add_argument( + "mozilla_central", + type=str, + help="Case sensitive absolute POSIX path to current mozilla-central repo", + ) + parser.add_argument( + "dtd", + type=str, + help="comma delimited list of case sensitive POSIX dtd file paths", + ) + parser.add_argument("description", type=str, help="string enclosed in quotes") + parser.add_argument( + "--dry-run", action="store_true", help="Tell if running dry run or not" + ) + parser.add_argument( + "--prefix", type=str, help="a keyword string to be added to all l10n ids" + ) + + parsed_args = parser.parse_args(sys_args) + + data["description"] = parsed_args.description + data["bug_id"] = parsed_args.bug_id + data["xul"] = parsed_args.xul + data["ftl"] = parsed_args.ftl + data["mozilla-central"] = parsed_args.mozilla_central + data["dtd"] = parsed_args.dtd.split(",") + data["dry-run"] = parsed_args.dry_run + data["prefix"] = parsed_args.prefix + data["recipe"] = "bug_{}_{}.py".format( + data["bug_id"], data["xul"].split("/")[-1].split(".")[0] + ) + + main() + + +def main(): + dry_run = data["dry-run"] + dtds = get_dtds(data["dtd"], data["mozilla-central"]) + + print("======== DTDs ========") + print(json.dumps(dtds, sort_keys=True, indent=2)) + + s = read_file(data["xul"], data["mozilla-central"]) + + print("======== INPUT ========") + print(s) + + print("======== OUTPUT ========") + (new_xul, messages) = collect_messages(s, data["prefix"]) + print(new_xul) + if not dry_run: + write_file(data["xul"], new_xul, data["mozilla-central"]) + + print("======== L10N ========") + + print(json.dumps(messages, sort_keys=True, indent=2)) + + migration = build_migration(messages, dtds, data) + + print("======== MIGRATION ========") + print(migration) + recipe_path = "{}/{}".format(data["migration"], data["recipe"]) + if not dry_run: + write_file(recipe_path, migration, data["mozilla-central"]) + + ftl = build_ftl(messages, dtds, data) + + print("======== Fluent ========") + print(ftl.encode("utf-8")) + if not dry_run: + write_file( + data["ftl"], ftl.encode("utf-8"), data["mozilla-central"], append=True + ) + + +if __name__ == "__main__": + parse_inputs() diff --git a/python/l10n/convert_xul_to_fluent/lib/__init__.py b/python/l10n/convert_xul_to_fluent/lib/__init__.py new file mode 100644 index 0000000000..443d56533f --- /dev/null +++ b/python/l10n/convert_xul_to_fluent/lib/__init__.py @@ -0,0 +1,7 @@ +# 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 __future__ import absolute_import + +# from .xul import collect_messages diff --git a/python/l10n/convert_xul_to_fluent/lib/dtd.py b/python/l10n/convert_xul_to_fluent/lib/dtd.py new file mode 100644 index 0000000000..85348e62ff --- /dev/null +++ b/python/l10n/convert_xul_to_fluent/lib/dtd.py @@ -0,0 +1,28 @@ +# 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 __future__ import absolute_import +from io import StringIO +from lxml import etree +from .utils import read_file + + +def get_dtds(sources, base_path): + entries = {} + for source in sources: + dtd = get_dtd(source, base_path) + for entry in dtd: + entries[entry] = {"value": dtd[entry], "file": source} + return entries + + +def get_dtd(dtd_source, base_path): + entries = {} + + source = read_file(dtd_source, base_path) + + dtd = etree.DTD(StringIO(source.decode("utf-8"))) + for entity in dtd.entities(): + entries[entity.name] = entity.content + return entries diff --git a/python/l10n/convert_xul_to_fluent/lib/fluent.py b/python/l10n/convert_xul_to_fluent/lib/fluent.py new file mode 100644 index 0000000000..ecab538579 --- /dev/null +++ b/python/l10n/convert_xul_to_fluent/lib/fluent.py @@ -0,0 +1,34 @@ +# 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 __future__ import absolute_import +from fluent.syntax import ast +from fluent.syntax.serializer import FluentSerializer + + +def get_value_from_dtd(name, dtd): + return dtd[name[1:-1]]["value"] + + +def build_ftl(messages, dtd, data): + res = ast.Resource() + + for id_str in messages: + msg = messages[id_str] + l10n_id = ast.Identifier(id_str) + val = None + attrs = [] + if msg["value"]: + dtd_val = get_value_from_dtd(msg["value"], dtd) + val = ast.Pattern([ast.TextElement(dtd_val)]) + for attr_name in msg["attrs"]: + dtd_val = get_value_from_dtd(msg["attrs"][attr_name], dtd) + attr_val = ast.Pattern([ast.TextElement(dtd_val)]) + attrs.append(ast.Attribute(ast.Identifier(attr_name), attr_val)) + + m = ast.Message(l10n_id, val, attrs) + res.body.append(m) + + serializer = FluentSerializer() + return serializer.serialize(res) diff --git a/python/l10n/convert_xul_to_fluent/lib/migration.py b/python/l10n/convert_xul_to_fluent/lib/migration.py new file mode 100644 index 0000000000..efe32c5178 --- /dev/null +++ b/python/l10n/convert_xul_to_fluent/lib/migration.py @@ -0,0 +1,77 @@ +from __future__ import absolute_import
+
+
+def to_chrome_path(path):
+ path = path.replace("/locales/en-US", "")
+ if path.startswith("./"):
+ path = path[2:]
+ return path
+
+
+def get_dtd_path(name, dtds):
+ return dtds[name[1:-1]]["file"]
+
+
+def get_entity_name(s):
+ return s[1:-1]
+
+
+def ind(n=0):
+ return " " * 4 * n
+
+
+def add_copy(dtd, entity_id):
+ result = "{ " + 'COPY("{0}", "{1}")'.format(dtd, entity_id) + " }\n"
+ return result
+
+
+def make_header():
+ res = "# coding=utf8\n\n"
+ res += "# Any copyright is dedicated to the Public Domain.\n"
+ res += "# http://creativecommons.org/publicdomain/zero/1.0/\n\n"
+ res += "from __future__ import absolute_import\n"
+ res += "import fluent.syntax.ast as FTL\n"
+ res += "from fluent.migrate.helpers import transforms_from\n"
+ res += "from fluent.migrate.helpers import MESSAGE_REFERENCE, "
+ res += "TERM_REFERENCE, VARIABLE_REFERENCE\n"
+ res += "from fluent.migrate import COPY, CONCAT, REPLACE\n"
+
+ return res
+
+
+def build_migration(messages, dtds, data):
+ res = make_header()
+ desc = "Bug {0} - {1}, part {{index}}.".format(data["bug_id"], data["description"])
+ res += '\n\ndef migrate(ctx):\n """{0}"""\n\n'.format(desc)
+
+ for dtd_path in data["dtd"]:
+ res += "{0}ctx.maybe_add_localization('{1}')\n".format(
+ ind(1), to_chrome_path(dtd_path)
+ )
+
+ res += "\n"
+ res += ind(1) + "ctx.add_transforms(\n"
+ res += ind(2) + "'{0}',\n".format(to_chrome_path(data["ftl"]))
+ res += ind(2) + "'{0}',\n".format(to_chrome_path(data["ftl"]))
+ res += ind(2) + "transforms_from(\n"
+ res += '"""\n'
+ for l10n_id in messages:
+ msg = messages[l10n_id]
+ if not msg["attrs"]:
+ entity = get_entity_name(msg["value"])
+ entity_path = to_chrome_path(get_dtd_path(msg["value"], dtds))
+ res += "{0} = {1}".format(l10n_id, add_copy(entity_path, entity))
+ else:
+ res += "{0} = \n".format(l10n_id)
+ for name in msg["attrs"]:
+ attr = msg["attrs"][name]
+ attr_name = get_entity_name(attr)
+ entity_path = to_chrome_path(get_dtd_path(attr, dtds))
+ res += "{0}.{1} = {2}".format(
+ ind(1), name, add_copy(entity_path, attr_name)
+ )
+ res += '"""\n'
+ res += ind(2) + ")\n"
+ res += ind(1) + ")"
+
+ return res
diff --git a/python/l10n/convert_xul_to_fluent/lib/utils.py b/python/l10n/convert_xul_to_fluent/lib/utils.py new file mode 100644 index 0000000000..f90b002c2e --- /dev/null +++ b/python/l10n/convert_xul_to_fluent/lib/utils.py @@ -0,0 +1,23 @@ +# 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 __future__ import absolute_import +import os + + +def read_file(path, base_path=None): + if base_path is not None: + path = os.path.join(base_path, path) + path = path.replace("\\", "/") + with open(path) as fptr: + return fptr.read() + + +def write_file(path, text, base_path=None, append=False): + if base_path is not None: + path = os.path.join(base_path, path) + path = path.replace("\\", "/") + mode = "a" if append and os.path.exists(path) else "w" + with open(path, mode) as text_file: + text_file.write(text) diff --git a/python/l10n/convert_xul_to_fluent/lib/xul.py b/python/l10n/convert_xul_to_fluent/lib/xul.py new file mode 100644 index 0000000000..cb940c6c17 --- /dev/null +++ b/python/l10n/convert_xul_to_fluent/lib/xul.py @@ -0,0 +1,117 @@ +# 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 __future__ import absolute_import +import re +import string + +tag_re = r"<([a-z]+[^>/]*)(/?>)([^<]*)(?:</[a-z]+>)?" +attr_re = r'\s+([a-z]+)="([\&\;\.a-zA-Z0-9]+)"' +prefix = "" + +messages = {} + + +def is_entity(s): + return s.startswith("&") and s.endswith(";") + + +def convert_camel_case(name): + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1-\2", name) + return re.sub("([a-z0-9])([A-Z])", r"\1-\2", s1).lower() + + +def construct_l10n_id(val, attrs): + global prefix + id = None + if val: + id = val[1:-1].strip(string.digits) + else: + core = None + for k in attrs: + v = attrs[k][1:-1].strip(string.digits).split(".") + if not core: + if v[-1].lower() == k: + core = ".".join(v[:-1]) + else: + core = ".".join(v) + if core: + id = core + id = id.replace(".", "-") + id = convert_camel_case(id) + if prefix: + id = "{}-{}".format(prefix, id) + return id + + +vector = 0 +is_l10n = False + + +def get_indent(pre_tag): + if "\n" not in pre_tag: + return " " + last_bl = pre_tag.rindex("\n") + indent = 0 + for ch in pre_tag[last_bl:]: + if ch == " ": + indent += 1 + return "\n" + " " * indent + + +def tagrepl(m): + global vector + global is_l10n + vector = 0 + + is_l10n = False + l10n_val = None + l10n_attrs = {} + if is_entity(m.group(3)): + is_l10n = True + l10n_val = m.group(3) + + def attrrepl(m2): + global vector + global is_l10n + attr_l10n = False + if is_entity(m2.group(2)): + attr_l10n = True + l10n_attrs[m2.group(1)] = m2.group(2) + if attr_l10n: + is_l10n = True + vector = vector + len(m2.group(0)) + return "" + return m2.group(0) + + tag = re.sub(attr_re, attrrepl, m.group(0)) + if is_l10n: + l10n_id = construct_l10n_id(l10n_val, l10n_attrs) + messages[l10n_id] = {"value": l10n_val, "attrs": l10n_attrs} + indent = get_indent(tag[0 : len(m.group(1)) + 1 - vector]) + tag = ( + tag[0 : len(m.group(1)) + 1 - vector] + + indent + + 'data-l10n-id="' + + l10n_id + + '"' + + m.group(2) + + (m.group(3) if not l10n_val else "") + + tag[len(m.group(1)) + 1 + len(m.group(2)) + len(m.group(3)) - vector :] + ) + return tag + + +def collect_messages(xul_source, in_prefix): + global messages + global prefix + messages = {} + prefix = in_prefix + + new_source = re.sub(tag_re, tagrepl, xul_source) + return (new_source, messages) + + +if __name__ == "__main__": + pass diff --git a/python/l10n/fluent_migrations/__init__.py b/python/l10n/fluent_migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/python/l10n/fluent_migrations/__init__.py diff --git a/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py new file mode 100644 index 0000000000..eb395d44a9 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py @@ -0,0 +1,41 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate.helpers import VARIABLE_REFERENCE +from fluent.migrate import COPY, REPLACE + + +def migrate(ctx): + """Bug 1552333 - Migrate strings from pipnss.properties to aboutCertError.ftl""" + ctx.add_transforms( + "browser/browser/aboutCertError.ftl", + "browser/browser/aboutCertError.ftl", + transforms_from( + """ +cert-error-symantec-distrust-admin = { COPY(from_path, "certErrorSymantecDistrustAdministrator") } +""", + from_path="security/manager/chrome/pipnss/pipnss.properties", + ), + ) + ctx.add_transforms( + "browser/browser/aboutCertError.ftl", + "browser/browser/aboutCertError.ftl", + [ + FTL.Message( + id=FTL.Identifier("cert-error-symantec-distrust-description"), + value=REPLACE( + "security/manager/chrome/pipnss/pipnss.properties", + "certErrorSymantecDistrustDescription1", + { + "%1$S": VARIABLE_REFERENCE("hostname"), + }, + normalize_printf=True, + ), + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1565574_protocol_handler_dialog.py b/python/l10n/fluent_migrations/bug_1565574_protocol_handler_dialog.py new file mode 100644 index 0000000000..f793221f50 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1565574_protocol_handler_dialog.py @@ -0,0 +1,37 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +from fluent.migrate.helpers import transforms_from + + +def migrate(ctx): + """Bug 1565574 - Migrate protocol handler dialog strings to fluent, part {index}.""" + + ctx.add_transforms( + "toolkit/toolkit/global/handlerDialog.ftl", + "toolkit/toolkit/global/handlerDialog.ftl", + transforms_from( + """ +choose-other-app-description = { COPY(from_path, "ChooseOtherApp.description") } +choose-app-btn = + .label = { COPY(from_path, "ChooseApp.label") } + .accessKey = { COPY(from_path, "ChooseApp.accessKey") } +""", + from_path="toolkit/chrome/mozapps/handling/handling.dtd", + ), + ) + + ctx.add_transforms( + "toolkit/toolkit/global/handlerDialog.ftl", + "toolkit/toolkit/global/handlerDialog.ftl", + transforms_from( + """ +choose-dialog-privatebrowsing-disabled = { COPY(from_path, "privatebrowsing.disabled.label") } +choose-other-app-window-title = { COPY(from_path, "choose.application.title") } +""", + from_path="toolkit/chrome/mozapps/handling/handling.properties", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1568133_menubar.py b/python/l10n/fluent_migrations/bug_1568133_menubar.py new file mode 100644 index 0000000000..44fc0af2f7 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1568133_menubar.py @@ -0,0 +1,141 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate import COPY, CONCAT, REPLACE +from fluent.migrate.helpers import TERM_REFERENCE + + +def migrate(ctx): + """Bug 1568133 - Migrate remaining menubar from dtd to ftl, part {index}""" + + ctx.add_transforms( + "browser/browser/menubar.ftl", + "browser/browser/menubar.ftl", + transforms_from( + """ +menu-application-services = + .label = { COPY(base_path, "servicesMenuMac.label") } +menu-application-hide-other = + .label = { COPY(base_path, "hideOtherAppsCmdMac.label") } +menu-application-show-all = + .label = { COPY(base_path, "showAllAppsCmdMac.label") } +menu-application-touch-bar = + .label = { COPY(base_path, "touchBarCmdMac.label") } + +menu-quit = + .label = + { PLATFORM() -> + [windows] { COPY(browser_path, "quitApplicationCmdWin2.label") } + *[other] { COPY(browser_path, "quitApplicationCmd.label") } + } + .accesskey = + { PLATFORM() -> + [windows] { COPY(browser_path, "quitApplicationCmdWin2.accesskey") } + *[other] { COPY(browser_path, "quitApplicationCmd.accesskey") } + } + +menu-quit-button = + .label = { menu-quit.label } +""", + base_path="browser/chrome/browser/baseMenuOverlay.dtd", + browser_path="browser/chrome/browser/browser.dtd", + ), + ) + + ctx.add_transforms( + "browser/browser/menubar.ftl", + "browser/browser/menubar.ftl", + [ + FTL.Message( + id=FTL.Identifier("menu-application-hide-this"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + "browser/chrome/browser/baseMenuOverlay.dtd", + "hideThisAppCmdMac2.label", + { + "&brandShorterName;": TERM_REFERENCE( + "brand-shorter-name" + ), + }, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("menu-quit-mac"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + "browser/chrome/browser/browser.dtd", + "quitApplicationCmdMac2.label", + { + "&brandShorterName;": TERM_REFERENCE( + "brand-shorter-name" + ), + }, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("menu-quit-button-win"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=FTL.Pattern( + elements=[ + FTL.Placeable( + expression=FTL.MessageReference( + id=FTL.Identifier("menu-quit"), + attribute=FTL.Identifier("label"), + ) + ) + ] + ), + ), + FTL.Attribute( + id=FTL.Identifier("tooltip"), + value=REPLACE( + "browser/chrome/browser/browser.dtd", + "quitApplicationCmdWin2.tooltip", + { + "&brandShorterName;": TERM_REFERENCE( + "brand-shorter-name" + ), + }, + ), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("menu-about"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + "browser/chrome/browser/baseMenuOverlay.dtd", + "aboutProduct2.label", + { + "&brandShorterName;": TERM_REFERENCE( + "brand-shorter-name" + ), + }, + ), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY( + "browser/chrome/browser/baseMenuOverlay.dtd", + "aboutProduct2.accesskey", + ), + ), + ], + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1634042_page_action_menu.py b/python/l10n/fluent_migrations/bug_1634042_page_action_menu.py new file mode 100644 index 0000000000..d4ade307d8 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1634042_page_action_menu.py @@ -0,0 +1,112 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +import fluent.syntax.ast as FTL +from fluent.migrate import COPY, PLURALS, REPLACE, REPLACE_IN_TEXT +from fluent.migrate.helpers import TERM_REFERENCE, VARIABLE_REFERENCE, transforms_from + + +def migrate(ctx): + """Bug 1634042 - Adding page action labels to fluent, part {index}.""" + + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + transforms_from( + """ +page-action-pin-tab-panel = + .label = { COPY(from_path, "pinTab.label") } +page-action-pin-tab-urlbar = + .tooltiptext = { COPY(from_path, "pinTab.label") } +page-action-unpin-tab-panel = + .label = { COPY(from_path, "unpinTab.label") } +page-action-unpin-tab-urlbar = + .tooltiptext = { COPY(from_path, "unpinTab.label") } +page-action-copy-url-panel = + .label = { COPY(from_path, "pageAction.copyLink.label") } +page-action-copy-url-urlbar = + .tooltiptext = { COPY(from_path, "pageAction.copyLink.label") } +page-action-email-link-panel = + .label = { COPY(from_path, "emailPageCmd.label") } +page-action-email-link-urlbar = + .tooltiptext = { COPY(from_path, "emailPageCmd.label") } +page-action-share-url-panel = + .label = { COPY(from_path, "pageAction.shareUrl.label") } +page-action-share-url-urlbar = + .tooltiptext = { COPY(from_path, "pageAction.shareUrl.label") } +page-action-share-more-panel = + .label = { COPY(from_path, "pageAction.shareMore.label") } +page-action-send-tab-not-ready = + .label = { COPY(from_path, "sendToDevice.syncNotReady.label") } +""", + from_path="browser/chrome/browser/browser.dtd", + ), + ) + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + [ + FTL.Message( + id=FTL.Identifier("page-action-pocket-panel"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + "browser/chrome/browser/browser.dtd", + "saveToPocketCmd.label", + { + "Pocket": TERM_REFERENCE("pocket-brand-name"), + }, + ), + ) + ], + ) + ], + ) + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + [ + FTL.Message( + id=FTL.Identifier("page-action-send-tabs-panel"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + "browser/chrome/browser/browser.properties", + "pageAction.sendTabsToDevice.label", + VARIABLE_REFERENCE("tabCount"), + lambda text: REPLACE_IN_TEXT( + text, {"#1": VARIABLE_REFERENCE("tabCount")} + ), + ), + ) + ], + ) + ], + ) + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + [ + FTL.Message( + id=FTL.Identifier("page-action-send-tabs-urlbar"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=PLURALS( + "browser/chrome/browser/browser.properties", + "pageAction.sendTabsToDevice.label", + VARIABLE_REFERENCE("tabCount"), + lambda text: REPLACE_IN_TEXT( + text, {"#1": VARIABLE_REFERENCE("tabCount")} + ), + ), + ) + ], + ) + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1658629_migration_urlbar_actions.py b/python/l10n/fluent_migrations/bug_1658629_migration_urlbar_actions.py new file mode 100644 index 0000000000..e7fecad144 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1658629_migration_urlbar_actions.py @@ -0,0 +1,60 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate import REPLACE +from fluent.migrate.helpers import VARIABLE_REFERENCE + + +def migrate(ctx): + """Bug 1658629 - Show proper action text when moving through local one-off search buttons, part {index}.""" + + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + [ + FTL.Message( + id=FTL.Identifier("urlbar-result-action-search-w-engine"), + value=REPLACE( + "toolkit/chrome/global/autocomplete.properties", + "searchWithEngine", + {"%1$S": VARIABLE_REFERENCE("engine")}, + normalize_printf=True, + ), + ) + ], + ) + + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + [ + FTL.Message( + id=FTL.Identifier("urlbar-result-action-search-in-private-w-engine"), + value=REPLACE( + "toolkit/chrome/global/autocomplete.properties", + "searchInPrivateWindowWithEngine", + {"%1$S": VARIABLE_REFERENCE("engine")}, + normalize_printf=True, + ), + ) + ], + ) + + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + transforms_from( + """ +urlbar-result-action-switch-tab = { COPY(from_path, "switchToTab2") } +urlbar-result-action-search-in-private = { COPY(from_path, "searchInPrivateWindow") } +urlbar-result-action-visit = { COPY(from_path, "visit") } + +""", + from_path="toolkit/chrome/global/autocomplete.properties", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1667781_preferences_dialogs.py b/python/l10n/fluent_migrations/bug_1667781_preferences_dialogs.py new file mode 100644 index 0000000000..b66d4ccf90 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1667781_preferences_dialogs.py @@ -0,0 +1,102 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +from fluent.migrate.helpers import transforms_from + + +def migrate(ctx): + """Bug 1667781 - Refactor preferences dialogs to use dialog element, part {index}.""" + + ctx.add_transforms( + "browser/browser/preferences/addEngine.ftl", + "browser/browser/preferences/addEngine.ftl", + transforms_from( + """ +add-engine-dialog = + .buttonlabelaccept = { COPY_PATTERN(from_path, "add-engine-ok.label") } + .buttonaccesskeyaccept = { COPY_PATTERN(from_path, "add-engine-ok.accesskey") } +""", + from_path="browser/browser/preferences/addEngine.ftl", + ), + ) + + ctx.add_transforms( + "browser/browser/preferences/clearSiteData.ftl", + "browser/browser/preferences/clearSiteData.ftl", + transforms_from( + """ +clear-site-data-dialog = + .buttonlabelaccept = { COPY_PATTERN(from_path, "clear-site-data-clear.label") } + .buttonaccesskeyaccept = { COPY_PATTERN(from_path, "clear-site-data-clear.accesskey") } +""", + from_path="browser/browser/preferences/clearSiteData.ftl", + ), + ) + + ctx.add_transforms( + "browser/browser/preferences/containers.ftl", + "browser/browser/preferences/containers.ftl", + transforms_from( + """ +containers-dialog = + .buttonlabelaccept = { COPY_PATTERN(from_path, "containers-button-done.label") } + .buttonaccesskeyaccept = { COPY_PATTERN(from_path, "containers-button-done.accesskey") } +""", + from_path="browser/browser/preferences/containers.ftl", + ), + ) + + ctx.add_transforms( + "browser/browser/preferences/permissions.ftl", + "browser/browser/preferences/permissions.ftl", + transforms_from( + """ +permission-dialog = + .buttonlabelaccept = { COPY_PATTERN(from_path, "permissions-button-ok.label") } + .buttonaccesskeyaccept = { COPY_PATTERN(from_path, "permissions-button-ok.accesskey") } +""", + from_path="browser/browser/preferences/permissions.ftl", + ), + ) + + ctx.add_transforms( + "browser/browser/preferences/siteDataSettings.ftl", + "browser/browser/preferences/siteDataSettings.ftl", + transforms_from( + """ +site-data-settings-dialog = + .buttonlabelaccept = { COPY_PATTERN(from_path, "site-data-button-save.label") } + .buttonaccesskeyaccept = { COPY_PATTERN(from_path, "site-data-button-save.accesskey") } +""", + from_path="browser/browser/preferences/siteDataSettings.ftl", + ), + ) + + ctx.add_transforms( + "browser/browser/preferences/translation.ftl", + "browser/browser/preferences/translation.ftl", + transforms_from( + """ +translation-dialog = + .buttonlabelaccept = { COPY_PATTERN(from_path, "translation-button-close.label") } + .buttonaccesskeyaccept = { COPY_PATTERN(from_path, "translation-button-close.accesskey") } +""", + from_path="browser/browser/preferences/translation.ftl", + ), + ) + + ctx.add_transforms( + "browser/browser/preferences/blocklists.ftl", + "browser/browser/preferences/blocklists.ftl", + transforms_from( + """ +blocklist-dialog = + .buttonlabelaccept = { COPY_PATTERN(from_path, "blocklist-button-ok.label") } + .buttonaccesskeyaccept = { COPY_PATTERN(from_path, "blocklist-button-ok.accesskey") } +""", + from_path="browser/browser/preferences/blocklists.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1668284_settings_change.py b/python/l10n/fluent_migrations/bug_1668284_settings_change.py new file mode 100644 index 0000000000..71611b16a6 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1668284_settings_change.py @@ -0,0 +1,66 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate import REPLACE, COPY +from fluent.migrate.helpers import MESSAGE_REFERENCE, TERM_REFERENCE + + +def migrate(ctx): + """Bug 1668284 - Unknown content type change settings label is no longer accurate, part {index}.""" + + ctx.add_transforms( + "toolkit/toolkit/global/unknownContentType.ftl", + "toolkit/toolkit/global/unknownContentType.ftl", + [ + FTL.Message( + id=FTL.Identifier("unknowncontenttype-settingschange"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("value"), + value=FTL.Pattern( + elements=[ + FTL.Placeable( + expression=FTL.SelectExpression( + selector=MESSAGE_REFERENCE("PLATFORM()"), + variants=[ + FTL.Variant( + key=FTL.Identifier("windows"), + default=False, + value=REPLACE( + "toolkit/chrome/mozapps/downloads/settingsChange.dtd", + "settingsChangeOptions.label", + { + "&brandShortName;": TERM_REFERENCE( + "brand-short-name" + ), + }, + ), + ), + FTL.Variant( + key=FTL.Identifier("other"), + default=True, + value=REPLACE( + "toolkit/chrome/mozapps/downloads/settingsChange.dtd", + "settingsChangePreferences.label", + { + "&brandShortName;": TERM_REFERENCE( + "brand-short-name" + ), + }, + ), + ), + ], + ) + ) + ] + ), + ) + ], + ) + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1682022_bookmarks_and_menubar_strings.py b/python/l10n/fluent_migrations/bug_1682022_bookmarks_and_menubar_strings.py new file mode 100644 index 0000000000..74ab8a248f --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1682022_bookmarks_and_menubar_strings.py @@ -0,0 +1,83 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +from fluent.migrate.helpers import transforms_from + + +def migrate(ctx): + """Bug 1682022 - Convert some bookmarking strings to Fluent, and copy over some menubar strings, part {index}.""" + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + transforms_from( + """ +bookmarks-show-all-bookmarks = + .label = { COPY(from_path, "showAllBookmarks2.label") } +bookmarks-recent-bookmarks = + .value = { COPY(from_path, "recentBookmarks.label") } +bookmarks-toolbar-chevron = + .tooltiptext = { COPY(from_path, "bookmarksToolbarChevron.tooltip") } +bookmarks-sidebar-content = + .aria-label = { COPY(from_path, "bookmarksButton.label") } +bookmarks-menu-button = + .label = { COPY(from_path, "bookmarksMenuButton2.label") } +bookmarks-other-bookmarks-menu = + .label = { COPY(from_path, "bookmarksMenuButton.other.label") } +bookmarks-mobile-bookmarks-menu = + .label = { COPY(from_path, "bookmarksMenuButton.mobile.label") } +bookmarks-tools-sidebar-visibility = + .label = { $isVisible -> + [true] { COPY(from_path, "hideBookmarksSidebar.label") } + *[other] { COPY(from_path, "viewBookmarksSidebar2.label") } + } +bookmarks-tools-toolbar-visibility = + .label = { $isVisible -> + [true] { COPY(from_path, "hideBookmarksToolbar.label") } + *[other] { COPY(from_path, "viewBookmarksToolbar.label") } + } +bookmarks-tools-menu-button-visibility = + .label = { $isVisible -> + [true] { COPY(from_path, "removeBookmarksMenu.label") } + *[other] { COPY(from_path, "addBookmarksMenu.label") } + } +bookmarks-search = + .label = { COPY(from_path, "searchBookmarks.label") } +bookmarks-tools = + .label = { COPY(from_path, "bookmarkingTools.label") } +bookmarks-toolbar = + .toolbarname = { COPY(from_path, "personalbarCmd.label") } + .accesskey = { COPY(from_path, "personalbarCmd.accesskey") } + .aria-label = { COPY(from_path, "personalbar.accessibleLabel") } +bookmarks-toolbar-menu = + .label = { COPY(from_path, "personalbarCmd.label") } +bookmarks-toolbar-placeholder = + .title = { COPY(from_path, "bookmarksToolbarItem.label") } +bookmarks-toolbar-placeholder-button = + .label = { COPY(from_path, "bookmarksToolbarItem.label") } +library-bookmarks-menu = + .label = { COPY(from_path, "bookmarksSubview.label") } +""", + from_path="browser/chrome/browser/browser.dtd", + ), + ) + + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + transforms_from( + """ +library-bookmarks-bookmark-this-page = + .label = { COPY_PATTERN(from_path, "menu-bookmark-this-page.label") } +library-bookmarks-bookmark-edit = + .label = { COPY_PATTERN(from_path, "menu-bookmark-edit.label") } + +more-menu-go-offline = + .label = { COPY_PATTERN(from_path, "menu-file-go-offline.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-file-go-offline.accesskey") } +""", + from_path="browser/browser/menubar.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1682022_context_menu_and_browser_strings.py b/python/l10n/fluent_migrations/bug_1682022_context_menu_and_browser_strings.py new file mode 100644 index 0000000000..0b0b68874c --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1682022_context_menu_and_browser_strings.py @@ -0,0 +1,38 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +from fluent.migrate.helpers import transforms_from + + +def migrate(ctx): + """Bug 1682022 - Fork more strings from the context menu and browser.dtd for the AppMenu, part {index}.""" + ctx.add_transforms( + "browser/browser/appmenu.ftl", + "browser/browser/appmenu.ftl", + transforms_from( + """ +appmenuitem-save-page = + .label = { COPY_PATTERN(from_path, "main-context-menu-page-save.label") } +""", + from_path="browser/browser/browserContext.ftl", + ), + ) + + ctx.add_transforms( + "browser/browser/appmenu.ftl", + "browser/browser/appmenu.ftl", + transforms_from( + """ +appmenuitem-new-window = + .label = { COPY(from_path, "newNavigatorCmd.label") } +appmenuitem-new-private-window = + .label = { COPY(from_path, "newPrivateWindow.label") } +appmenuitem-fullscreen = + .label = { COPY(from_path, "fullScreenCmd.label") } +""", + from_path="browser/chrome/browser/browser.dtd", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1683419_forking_help_menu.py b/python/l10n/fluent_migrations/bug_1683419_forking_help_menu.py new file mode 100644 index 0000000000..65e6edbe47 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1683419_forking_help_menu.py @@ -0,0 +1,55 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +from fluent.migrate.helpers import transforms_from + + +def migrate(ctx): + """Bug 1683419 - Fork the Help menu strings for use in the AppMenu, part {index}.""" + ctx.add_transforms( + "browser/browser/appmenu.ftl", + "browser/browser/appmenu.ftl", + transforms_from( + """ +appmenu-about = + .label = { COPY_PATTERN(from_path, "menu-about.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-about.accesskey") } +appmenu-help-product = + .label = { COPY_PATTERN(from_path, "menu-help-product.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-product.accesskey") } +appmenu-help-show-tour = + .label = { COPY_PATTERN(from_path, "menu-help-show-tour.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-show-tour.accesskey") } +appmenu-help-import-from-another-browser = + .label = { COPY_PATTERN(from_path, "menu-help-import-from-another-browser.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-import-from-another-browser.accesskey") } +appmenu-help-keyboard-shortcuts = + .label = { COPY_PATTERN(from_path, "menu-help-keyboard-shortcuts.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-keyboard-shortcuts.accesskey") } +appmenu-help-troubleshooting-info = + .label = { COPY_PATTERN(from_path, "menu-help-troubleshooting-info.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-troubleshooting-info.accesskey") } +appmenu-help-feedback-page = + .label = { COPY_PATTERN(from_path, "menu-help-feedback-page.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-feedback-page.accesskey") } +appmenu-help-safe-mode-without-addons = + .label = { COPY_PATTERN(from_path, "menu-help-safe-mode-without-addons.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-safe-mode-without-addons.accesskey") } +appmenu-help-safe-mode-with-addons = + .label = { COPY_PATTERN(from_path, "menu-help-safe-mode-with-addons.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-safe-mode-with-addons.accesskey") } +appmenu-help-report-deceptive-site = + .label = { COPY_PATTERN(from_path, "menu-help-report-deceptive-site.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-report-deceptive-site.accesskey") } +appmenu-help-not-deceptive = + .label = { COPY_PATTERN(from_path, "menu-help-not-deceptive.label") } + .accesskey = { COPY_PATTERN(from_path, "menu-help-not-deceptive.accesskey") } +appmenu-help-check-for-update = + .label = { COPY_PATTERN(from_path, "menu-help-check-for-update.label") } +""", + from_path="browser/browser/menubar.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1686331_library_recent_activity.py b/python/l10n/fluent_migrations/bug_1686331_library_recent_activity.py new file mode 100644 index 0000000000..baaad1eec8 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1686331_library_recent_activity.py @@ -0,0 +1,23 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from __future__ import absolute_import +from fluent.migrate.helpers import transforms_from +from fluent.migrate import COPY_PATTERN + + +def migrate(ctx): + """Bug 1686331 - Library menu should not have a scroll bar part {index}.""" + ctx.add_transforms( + "browser/browser/browser.ftl", + "browser/browser/browser.ftl", + transforms_from( + """ +library-recent-activity-title = + .value = {COPY_PATTERN(from_path, "library-recent-activity-label")} +""", + from_path="browser/browser/browser.ftl", + ), + ) diff --git a/python/l10n/test_fluent_migrations/__init__.py b/python/l10n/test_fluent_migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/python/l10n/test_fluent_migrations/__init__.py diff --git a/python/l10n/test_fluent_migrations/fmt.py b/python/l10n/test_fluent_migrations/fmt.py new file mode 100644 index 0000000000..98c41f2ab8 --- /dev/null +++ b/python/l10n/test_fluent_migrations/fmt.py @@ -0,0 +1,190 @@ +from __future__ import absolute_import, print_function +import codecs +from difflib import unified_diff +import logging +import os +import re +import shutil +import sys + +import hglib +from mozboot.util import get_state_dir +import mozpack.path as mozpath + +from compare_locales.merge import merge_channels +from compare_locales.paths.files import ProjectFiles +from compare_locales.paths.configparser import TOMLParser +from fluent.migrate import validator +from fluent.syntax import FluentParser, FluentSerializer + + +def inspect_migration(path): + """Validate recipe and extract some metadata.""" + return validator.Validator.validate(path) + + +def prepare_object_dir(cmd): + """Prepare object dir to have an up-to-date clone of gecko-strings. + + We run this once per mach invocation, for all tested migrations. + """ + obj_dir = mozpath.join(cmd.topobjdir, "python", "l10n") + if not os.path.exists(obj_dir): + os.makedirs(obj_dir) + state_dir = get_state_dir() + if os.path.exists(mozpath.join(state_dir, "gecko-strings")): + cmd.run_process( + ["hg", "pull", "-u"], cwd=mozpath.join(state_dir, "gecko-strings") + ) + else: + cmd.run_process( + ["hg", "clone", "https://hg.mozilla.org/l10n/gecko-strings"], + cwd=state_dir, + ) + return obj_dir + + +def diff_resources(left_path, right_path): + parser = FluentParser(with_spans=False) + serializer = FluentSerializer(with_junk=True) + lines = [] + for p in (left_path, right_path): + with codecs.open(p, encoding="utf-8") as fh: + res = parser.parse(fh.read()) + lines.append(serializer.serialize(res).splitlines(True)) + sys.stdout.writelines( + chunk for chunk in unified_diff(lines[0], lines[1], left_path, right_path) + ) + + +def test_migration(cmd, obj_dir, to_test, references): + """Test the given recipe. + + This creates a workdir by l10n-merging gecko-strings and the m-c source, + to mimmic gecko-strings after the patch to test landed. + It then runs the recipe with a gecko-strings clone as localization, both + dry and wet. + It inspects the generated commits, and shows a diff between the merged + reference and the generated content. + The diff is intended to be visually inspected. Some changes might be + expected, in particular when formatting of the en-US strings is different. + """ + rv = 0 + migration_name = os.path.splitext(os.path.split(to_test)[1])[0] + work_dir = mozpath.join(obj_dir, migration_name) + + paths = os.path.normpath(to_test).split(os.sep) + # Migration modules should be in a sub-folder of l10n. + migration_module = ( + ".".join(paths[paths.index("l10n") + 1 : -1]) + "." + migration_name + ) + + if os.path.exists(work_dir): + shutil.rmtree(work_dir) + os.makedirs(mozpath.join(work_dir, "reference")) + l10n_toml = mozpath.join( + cmd.topsrcdir, cmd.substs["MOZ_BUILD_APP"], "locales", "l10n.toml" + ) + pc = TOMLParser().parse(l10n_toml, env={"l10n_base": work_dir}) + pc.set_locales(["reference"]) + files = ProjectFiles("reference", [pc]) + for ref in references: + if ref != mozpath.normpath(ref): + cmd.log( + logging.ERROR, + "fluent-migration-test", + { + "file": to_test, + "ref": ref, + }, + 'Reference path "{ref}" needs to be normalized for {file}', + ) + rv = 1 + continue + full_ref = mozpath.join(work_dir, "reference", ref) + m = files.match(full_ref) + if m is None: + raise ValueError("Bad reference path: " + ref) + m_c_path = m[1] + g_s_path = mozpath.join(work_dir, "gecko-strings", ref) + resources = [ + b"" if not os.path.exists(f) else open(f, "rb").read() + for f in (g_s_path, m_c_path) + ] + ref_dir = os.path.dirname(full_ref) + if not os.path.exists(ref_dir): + os.makedirs(ref_dir) + open(full_ref, "wb").write(merge_channels(ref, resources)) + client = hglib.clone( + source=mozpath.join(get_state_dir(), "gecko-strings"), + dest=mozpath.join(work_dir, "en-US"), + ) + client.open() + old_tip = client.tip().node + run_migration = [ + cmd._virtualenv_manager.python_path, + "-m", + "fluent.migrate.tool", + "--lang", + "en-US", + "--reference-dir", + mozpath.join(work_dir, "reference"), + "--localization-dir", + mozpath.join(work_dir, "en-US"), + "--dry-run", + migration_module, + ] + cmd.run_process( + run_migration, + cwd=work_dir, + line_handler=print, + ) + # drop --dry-run + run_migration.pop(-2) + cmd.run_process( + run_migration, + cwd=work_dir, + line_handler=print, + ) + tip = client.tip().node + if old_tip == tip: + cmd.log( + logging.WARN, + "fluent-migration-test", + { + "file": to_test, + }, + "No migration applied for {file}", + ) + return rv + for ref in references: + diff_resources( + mozpath.join(work_dir, "reference", ref), + mozpath.join(work_dir, "en-US", ref), + ) + messages = [ + l.desc.decode("utf-8") for l in client.log(b"::%s - ::%s" % (tip, old_tip)) + ] + bug = re.search("[0-9]{5,}", migration_name).group() + # Just check first message for bug number, they're all following the same pattern + if bug not in messages[0]: + rv = 1 + cmd.log( + logging.ERROR, + "fluent-migration-test", + { + "file": to_test, + }, + "Missing or wrong bug number for {file}", + ) + if any("part {}".format(n + 1) not in msg for n, msg in enumerate(messages)): + rv = 1 + cmd.log( + logging.ERROR, + "fluent-migration-test", + { + "file": to_test, + }, + 'Commit messages should have "part {{index}}" for {file}', + ) + return rv |