diff options
Diffstat (limited to '')
23 files changed, 2533 insertions, 0 deletions
diff --git a/python/l10n/fluent_migrations/__init__.py b/python/l10n/fluent_migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/python/l10n/fluent_migrations/__init__.py diff --git a/python/l10n/fluent_migrations/bug_1347955_aboutLogging.py b/python/l10n/fluent_migrations/bug_1347955_aboutLogging.py new file mode 100644 index 0000000000..a10aa1ae8d --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1347955_aboutLogging.py @@ -0,0 +1,35 @@ +# 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_PATTERN + + +def migrate(ctx): + """Bug 1347955 - Move the logging sub-page of about:networking to about:logging, part {index}""" + ctx.add_transforms( + "toolkit/toolkit/about/aboutLogging.ftl", + "toolkit/toolkit/about/aboutLogging.ftl", + transforms_from( + """ +about-logging-current-log-file = + {COPY_PATTERN(from_path, "about-networking-current-log-file")} +about-logging-current-log-modules = + {COPY_PATTERN(from_path, "about-networking-current-log-modules")} +about-logging-set-log-file = + {COPY_PATTERN(from_path, "about-networking-set-log-file")} +about-logging-set-log-modules = + {COPY_PATTERN(from_path, "about-networking-set-log-modules")} +about-logging-start-logging = + {COPY_PATTERN(from_path, "about-networking-start-logging")} +about-logging-stop-logging = + {COPY_PATTERN(from_path, "about-networking-stop-logging")} +about-logging-log-tutorial = + {COPY_PATTERN(from_path, "about-networking-log-tutorial")} + """, + from_path="toolkit/toolkit/about/aboutNetworking.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py new file mode 100644 index 0000000000..5c8300e01f --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1552333_aboutCertError.py @@ -0,0 +1,40 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate.helpers import VARIABLE_REFERENCE +from fluent.migrate import COPY, REPLACE + + +def migrate(ctx): + """Bug 1552333 - Migrate strings from pipnss.properties to aboutCertError.ftl""" + ctx.add_transforms( + "browser/browser/aboutCertError.ftl", + "browser/browser/aboutCertError.ftl", + transforms_from( + """ +cert-error-symantec-distrust-admin = { COPY(from_path, "certErrorSymantecDistrustAdministrator") } +""", + from_path="security/manager/chrome/pipnss/pipnss.properties", + ), + ) + ctx.add_transforms( + "browser/browser/aboutCertError.ftl", + "browser/browser/aboutCertError.ftl", + [ + FTL.Message( + id=FTL.Identifier("cert-error-symantec-distrust-description"), + value=REPLACE( + "security/manager/chrome/pipnss/pipnss.properties", + "certErrorSymantecDistrustDescription1", + { + "%1$S": VARIABLE_REFERENCE("hostname"), + }, + normalize_printf=True, + ), + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1577257_profiler_title.py b/python/l10n/fluent_migrations/bug_1577257_profiler_title.py new file mode 100644 index 0000000000..4091e596da --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1577257_profiler_title.py @@ -0,0 +1,22 @@ +# 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_PATTERN + + +def migrate(ctx): + """Bug 1577257 - Share logic behind panel headers across the UI, part {index}""" + ctx.add_transforms( + "browser/browser/appmenu.ftl", + "browser/browser/appmenu.ftl", + transforms_from( + """ +profiler-popup-header-text = + { COPY_PATTERN(from_path, "profiler-popup-title.value") } +""", + from_path="browser/browser/appmenu.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1635548_browser_context.py b/python/l10n/fluent_migrations/bug_1635548_browser_context.py new file mode 100644 index 0000000000..33bd0efc95 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1635548_browser_context.py @@ -0,0 +1,82 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from, VARIABLE_REFERENCE +from fluent.migrate import REPLACE, COPY + + +def migrate(ctx): + """Bug 1635548 - Migrate browser-context.inc to Fluent, part {index}""" + target = "toolkit/toolkit/global/textActions.ftl" + reference = "toolkit/toolkit/global/textActions.ftl" + ctx.add_transforms( + target, + reference, + transforms_from( + """ +text-action-spell-add-to-dictionary = + .label = { COPY(from_path, "spellAddToDictionary.label") } + .accesskey = { COPY(from_path, "spellAddToDictionary.accesskey") } + +text-action-spell-undo-add-to-dictionary = + .label = { COPY(from_path, "spellUndoAddToDictionary.label") } + .accesskey = { COPY(from_path, "spellUndoAddToDictionary.accesskey") } + +text-action-spell-check-toggle = + .label = { COPY(from_path, "spellCheckToggle.label") } + .accesskey = { COPY(from_path, "spellCheckToggle.accesskey") } + +text-action-spell-dictionaries = + .label = { COPY(from_path, "spellDictionaries.label") } + .accesskey = { COPY(from_path, "spellDictionaries.accesskey") } +""", + from_path="toolkit/chrome/global/textcontext.dtd", + ), + ) + + target = "toolkit/toolkit/global/textActions.ftl" + reference = "toolkit/toolkit/global/textActions.ftl" + ctx.add_transforms( + target, + reference, + transforms_from( + """ +text-action-spell-add-dictionaries = + .label = { COPY(from_path, "spellAddDictionaries.label") } + .accesskey = { COPY(from_path, "spellAddDictionaries.accesskey") } +""", + from_path="browser/chrome/browser/browser.dtd", + ), + ) + + target = "browser/browser/browserContext.ftl" + reference = "browser/browser/browserContext.ftl" + ctx.add_transforms( + target, + reference, + [ + FTL.Message( + id=FTL.Identifier("main-context-menu-open-link-in-container-tab"), + attributes=[ + FTL.Attribute( + FTL.Identifier("label"), + REPLACE( + "browser/chrome/browser/browser.properties", + "userContextOpenLink.label", + {"%1$S": VARIABLE_REFERENCE("containerName")}, + ), + ), + FTL.Attribute( + FTL.Identifier("accesskey"), + COPY( + "browser/chrome/browser/browser.dtd", + "openLinkCmdInTab.accesskey", + ), + ), + ], + ) + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1760029_tabbrowser.py b/python/l10n/fluent_migrations/bug_1760029_tabbrowser.py new file mode 100644 index 0000000000..9fe8cf3a7d --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1760029_tabbrowser.py @@ -0,0 +1,399 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import TERM_REFERENCE, VARIABLE_REFERENCE +from fluent.migrate.transforms import COPY, PLURALS, REPLACE, REPLACE_IN_TEXT, Transform + + +def migrate(ctx): + """Bug 1760029 - Migrate tabbrowser to Fluent, part {index}.""" + + source = "browser/chrome/browser/tabbrowser.properties" + target = "browser/browser/tabbrowser.ftl" + + browser_source = "browser/chrome/browser/browser.properties" + browser_target = "browser/browser/browser.ftl" + + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("tabbrowser-empty-tab-title"), + value=COPY(source, "tabs.emptyTabTitle"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-empty-private-tab-title"), + value=COPY(source, "tabs.emptyPrivateTabTitle2"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-menuitem-close-tab"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), value=COPY(source, "tabs.closeTab") + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-menuitem-close"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), value=COPY(source, "tabs.close") + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-container-tab-title"), + value=REPLACE( + source, + "tabs.containers.tooltip", + { + "%1$S": VARIABLE_REFERENCE("title"), + "%2$S": VARIABLE_REFERENCE("containerName"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-tab-tooltip"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=FTL.Pattern([FTL.Placeable(VARIABLE_REFERENCE("title"))]), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-close-tabs-tooltip"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "tabs.closeTabs.tooltip", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-mute-tab-audio-tooltip"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "tabs.muteAudio2.tooltip", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda text: REPLACE_IN_TEXT( + text, + { + "#1": VARIABLE_REFERENCE("tabCount"), + "%S": VARIABLE_REFERENCE("shortcut"), + }, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-unmute-tab-audio-tooltip"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "tabs.unmuteAudio2.tooltip", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda text: REPLACE_IN_TEXT( + text, + { + "#1": VARIABLE_REFERENCE("tabCount"), + "%S": VARIABLE_REFERENCE("shortcut"), + }, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-mute-tab-audio-background-tooltip"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "tabs.muteAudio2.background.tooltip", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-unmute-tab-audio-background-tooltip"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "tabs.unmuteAudio2.background.tooltip", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-unblock-tab-audio-tooltip"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "tabs.unblockAudio2.tooltip", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-tabs-title"), + value=PLURALS( + source, + "tabs.closeTabsTitle", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-tabs-button"), + value=COPY(source, "tabs.closeButtonMultiple"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-tabs-checkbox"), + value=COPY(source, "tabs.closeTabsConfirmCheckbox"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-windows-title"), + value=PLURALS( + source, + "tabs.closeWindowsTitle", + VARIABLE_REFERENCE("windowCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("windowCount")}, + ), + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-windows-button"), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=FTL.FunctionReference( + id=FTL.Identifier("PLATFORM"), arguments=FTL.CallArguments() + ), + variants=[ + FTL.Variant( + key=FTL.Identifier("windows"), + value=COPY(source, "tabs.closeWindowsButtonWin"), + ), + FTL.Variant( + key=FTL.Identifier("other"), + value=COPY(source, "tabs.closeWindowsButton"), + default=True, + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-tabs-with-key-title"), + value=REPLACE( + source, + "tabs.closeTabsWithKeyTitle", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-tabs-with-key-button"), + value=REPLACE( + source, + "tabs.closeTabsWithKeyButton", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-close-tabs-with-key-checkbox"), + value=REPLACE( + source, + "tabs.closeTabsWithKeyConfirmCheckbox", + {"%1$S": VARIABLE_REFERENCE("quitKey")}, + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-open-multiple-tabs-title"), + value=COPY(source, "tabs.openWarningTitle"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-open-multiple-tabs-message"), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=VARIABLE_REFERENCE("tabCount"), + variants=[ + FTL.Variant( + key=FTL.Identifier("other"), + value=REPLACE( + source, + "tabs.openWarningMultipleBranded", + { + "%1$S": VARIABLE_REFERENCE("tabCount"), + "%2$S": TERM_REFERENCE("brand-short-name"), + ". ": FTL.TextElement(". "), + }, + ), + default=True, + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-open-multiple-tabs-button"), + value=COPY(source, "tabs.openButtonMultiple"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-open-multiple-tabs-checkbox"), + value=REPLACE( + source, + "tabs.openWarningPromptMeBranded", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-caretbrowsing-title"), + value=COPY(source, "browsewithcaret.checkWindowTitle"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-caretbrowsing-message"), + value=COPY(source, "browsewithcaret.checkLabel"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-confirm-caretbrowsing-checkbox"), + value=COPY(source, "browsewithcaret.checkMsg"), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-customizemode-tab-title"), + value=REPLACE( + browser_source, + "customizeMode.tabTitle", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-context-mute-tab"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser_source, "muteTab.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser_source, "muteTab.accesskey"), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-context-unmute-tab"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser_source, "unmuteTab.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser_source, "unmuteTab.accesskey"), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-context-mute-selected-tabs"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser_source, "muteSelectedTabs2.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser_source, "muteSelectedTabs2.accesskey"), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("tabbrowser-context-unmute-selected-tabs"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser_source, "unmuteSelectedTabs2.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser_source, "unmuteSelectedTabs2.accesskey"), + ), + ], + ), + ], + ) + + ctx.add_transforms( + browser_target, + browser_target, + [ + FTL.Message( + id=FTL.Identifier("refresh-blocked-refresh-label"), + value=REPLACE( + browser_source, + "refreshBlocked.refreshLabel", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("refresh-blocked-redirect-label"), + value=REPLACE( + browser_source, + "refreshBlocked.redirectLabel", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("refresh-blocked-allow"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser_source, "refreshBlocked.goButton"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser_source, "refreshBlocked.goButton.accesskey"), + ), + ], + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1780585_pipFocusedstate.py b/python/l10n/fluent_migrations/bug_1780585_pipFocusedstate.py new file mode 100644 index 0000000000..6b1e706918 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1780585_pipFocusedstate.py @@ -0,0 +1,54 @@ +# coding=utf8 + +# 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 1780585 - Enhanced focused states on PiP controls, part {index}.""" + + ctx.add_transforms( + "toolkit/toolkit/pictureinpicture/pictureinpicture.ftl", + "toolkit/toolkit/pictureinpicture/pictureinpicture.ftl", + transforms_from( + """ +pictureinpicture-pause-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-pause-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-pause-cmd.title")} +pictureinpicture-play-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-play-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-play-cmd.title")} +pictureinpicture-mute-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-mute-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-mute-cmd.title")} +pictureinpicture-unmute-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-unmute-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-unmute-cmd.title")} +pictureinpicture-unpip-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-unpip-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-unpip-cmd.title")} +pictureinpicture-close-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-close-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-close-cmd.title")} +pictureinpicture-subtitles-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-subtitles-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-subtitles-cmd.title")} +pictureinpicture-fullscreen-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-fullscreen-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-fullscreen-cmd.title")} +pictureinpicture-exit-fullscreen-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-exit-fullscreen-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-exit-fullscreen-cmd.title")} +pictureinpicture-seekbackward-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-seekbackward-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-seekbackward-cmd.title")} +pictureinpicture-seekforward-btn = + .aria-label = {COPY_PATTERN(from_path, "pictureinpicture-seekforward-cmd.aria-label")} + .tooltip = {COPY_PATTERN(from_path, "pictureinpicture-seekforward-cmd.title")} + """, + from_path="toolkit/toolkit/pictureinpicture/pictureinpicture.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py b/python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py new file mode 100644 index 0000000000..99c6673f92 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1786186_mobile_aboutConfig.py @@ -0,0 +1,65 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate.transforms import COPY + + +def migrate(ctx): + """Bug 1786186 - Migrate mobile about:config to Fluent, part {index}""" + + target = "mobile/android/mobile/android/aboutConfig.ftl" + + ctx.add_transforms( + target, + target, + transforms_from( + """ + +config-toolbar-search = + .placeholder = { COPY(path1, "toolbar.searchPlaceholder") } +config-new-pref-name = + .placeholder = { COPY(path1, "newPref.namePlaceholder") } +config-new-pref-value-boolean = { COPY(path1, "newPref.valueBoolean") } +config-new-pref-value-string = { COPY(path1, "newPref.valueString") } +config-new-pref-value-integer = { COPY(path1, "newPref.valueInteger") } +config-new-pref-string = + .placeholder = { COPY(path1, "newPref.stringPlaceholder") } +config-new-pref-number = + .placeholder = { COPY(path1, "newPref.numberPlaceholder") } +config-new-pref-cancel-button = { COPY(path1, "newPref.cancelButton") } +config-context-menu-copy-pref-name = + .label = { COPY(path1, "contextMenu.copyPrefName") } +config-context-menu-copy-pref-value = + .label = { COPY(path1, "contextMenu.copyPrefValue") } +""", + path1="mobile/android/chrome/config.dtd", + ), + ) + + source = "mobile/android/chrome/config.properties" + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("config-new-pref-create-button"), + value=COPY(source, "newPref.createButton"), + ), + FTL.Message( + id=FTL.Identifier("config-new-pref-change-button"), + value=COPY(source, "newPref.changeButton"), + ), + FTL.Message( + id=FTL.Identifier("config-pref-toggle-button"), + value=COPY(source, "pref.toggleButton"), + ), + FTL.Message( + id=FTL.Identifier("config-pref-reset-button"), + value=COPY(source, "pref.resetButton"), + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1790387_featureCallout_a11y.py b/python/l10n/fluent_migrations/bug_1790387_featureCallout_a11y.py new file mode 100644 index 0000000000..0d6d839acd --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1790387_featureCallout_a11y.py @@ -0,0 +1,22 @@ +# coding=utf8 + +# 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 1790387 - A11y improvements for the Feature Callout step indicator, part {index}""" + ctx.add_transforms( + "browser/browser/newtab/onboarding.ftl", + "browser/browser/newtab/onboarding.ftl", + transforms_from( + """ +onboarding-welcome-steps-indicator-label = + .aria-label = { COPY_PATTERN(from_path, "onboarding-welcome-steps-indicator2.aria-valuetext") } +""", + from_path="browser/browser/newtab/onboarding.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1791178_downloadUI.py b/python/l10n/fluent_migrations/bug_1791178_downloadUI.py new file mode 100644 index 0000000000..d6256889f2 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1791178_downloadUI.py @@ -0,0 +1,187 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import VARIABLE_REFERENCE +from fluent.migrate.transforms import COPY, REPLACE, Transform + + +def migrate(ctx): + """Bug 1791178 - Convert DownloadUIHelper.jsm to Fluent, part {index}.""" + + source = "toolkit/chrome/mozapps/downloads/downloads.properties" + target = "toolkit/toolkit/downloads/downloadUI.ftl" + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("download-ui-confirm-title"), + value=COPY(source, "quitCancelDownloadsAlertTitle"), + ), + FTL.Message( + id=FTL.Identifier("download-ui-confirm-quit-cancel-downloads"), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=VARIABLE_REFERENCE("downloadsCount"), + variants=[ + FTL.Variant( + key=FTL.Identifier("1"), + value=COPY(source, "quitCancelDownloadsAlertMsg"), + ), + FTL.Variant( + key=FTL.Identifier("other"), + default=True, + value=REPLACE( + source, + "quitCancelDownloadsAlertMsgMultiple", + {"%1$S": VARIABLE_REFERENCE("downloadsCount")}, + ), + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier("download-ui-confirm-quit-cancel-downloads-mac"), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=VARIABLE_REFERENCE("downloadsCount"), + variants=[ + FTL.Variant( + key=FTL.Identifier("1"), + value=COPY(source, "quitCancelDownloadsAlertMsgMac"), + ), + FTL.Variant( + key=FTL.Identifier("other"), + default=True, + value=REPLACE( + source, + "quitCancelDownloadsAlertMsgMacMultiple", + {"%1$S": VARIABLE_REFERENCE("downloadsCount")}, + ), + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier("download-ui-confirm-offline-cancel-downloads"), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=VARIABLE_REFERENCE("downloadsCount"), + variants=[ + FTL.Variant( + key=FTL.Identifier("1"), + value=COPY(source, "offlineCancelDownloadsAlertMsg"), + ), + FTL.Variant( + key=FTL.Identifier("other"), + default=True, + value=REPLACE( + source, + "offlineCancelDownloadsAlertMsgMultiple", + {"%1$S": VARIABLE_REFERENCE("downloadsCount")}, + ), + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier( + "download-ui-confirm-leave-private-browsing-windows-cancel-downloads" + ), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=VARIABLE_REFERENCE("downloadsCount"), + variants=[ + FTL.Variant( + key=FTL.Identifier("1"), + value=COPY( + source, + "leavePrivateBrowsingWindowsCancelDownloadsAlertMsg2", + ), + ), + FTL.Variant( + key=FTL.Identifier("other"), + default=True, + value=REPLACE( + source, + "leavePrivateBrowsingWindowsCancelDownloadsAlertMsgMultiple2", + {"%1$S": VARIABLE_REFERENCE("downloadsCount")}, + ), + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier("download-ui-cancel-downloads-ok"), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=VARIABLE_REFERENCE("downloadsCount"), + variants=[ + FTL.Variant( + key=FTL.Identifier("1"), + value=COPY(source, "cancelDownloadsOKText"), + ), + FTL.Variant( + key=FTL.Identifier("other"), + default=True, + value=REPLACE( + source, + "cancelDownloadsOKTextMultiple", + {"%1$S": VARIABLE_REFERENCE("downloadsCount")}, + ), + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier("download-ui-dont-quit-button"), + value=Transform.pattern_of( + FTL.SelectExpression( + selector=FTL.FunctionReference( + id=FTL.Identifier("PLATFORM"), arguments=FTL.CallArguments() + ), + variants=[ + FTL.Variant( + key=FTL.Identifier("mac"), + value=COPY(source, "dontQuitButtonMac"), + ), + FTL.Variant( + key=FTL.Identifier("other"), + default=True, + value=COPY(source, "dontQuitButtonWin"), + ), + ], + ) + ), + ), + FTL.Message( + id=FTL.Identifier("download-ui-dont-go-offline-button"), + value=COPY(source, "dontGoOfflineButton"), + ), + FTL.Message( + id=FTL.Identifier("download-ui-dont-leave-private-browsing-button"), + value=COPY(source, "dontLeavePrivateBrowsingButton2"), + ), + FTL.Message( + id=FTL.Identifier("download-ui-file-executable-security-warning-title"), + value=COPY(source, "fileExecutableSecurityWarningTitle"), + ), + FTL.Message( + id=FTL.Identifier("download-ui-file-executable-security-warning"), + value=REPLACE( + source, + "fileExecutableSecurityWarning", + { + "%1$S": VARIABLE_REFERENCE("executable"), + "%2$S": VARIABLE_REFERENCE("executable"), + }, + ), + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1791178_downloadUtils.py b/python/l10n/fluent_migrations/bug_1791178_downloadUtils.py new file mode 100644 index 0000000000..d6cb174a86 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1791178_downloadUtils.py @@ -0,0 +1,172 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import VARIABLE_REFERENCE +from fluent.migrate.transforms import COPY, PLURALS, REPLACE + + +def migrate(ctx): + """Bug 1791178 - Convert DownloadUtils.jsm to Fluent, part {index}.""" + + source = "toolkit/chrome/mozapps/downloads/downloads.properties" + target = "toolkit/toolkit/downloads/downloadUtils.ftl" + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("download-utils-short-seconds"), + value=PLURALS(source, "shortSeconds", VARIABLE_REFERENCE("timeValue")), + ), + FTL.Message( + id=FTL.Identifier("download-utils-short-minutes"), + value=PLURALS(source, "shortMinutes", VARIABLE_REFERENCE("timeValue")), + ), + FTL.Message( + id=FTL.Identifier("download-utils-short-hours"), + value=PLURALS(source, "shortHours", VARIABLE_REFERENCE("timeValue")), + ), + FTL.Message( + id=FTL.Identifier("download-utils-short-days"), + value=PLURALS(source, "shortDays", VARIABLE_REFERENCE("timeValue")), + ), + FTL.Message( + id=FTL.Identifier("download-utils-status"), + value=REPLACE( + source, + "statusFormat3", + { + "%4$S": VARIABLE_REFERENCE("timeLeft"), + "%1$S": VARIABLE_REFERENCE("transfer"), + "%2$S": VARIABLE_REFERENCE("rate"), + "%3$S": VARIABLE_REFERENCE("unit"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-status-infinite-rate"), + value=REPLACE( + source, + "statusFormatInfiniteRate", + { + "%3$S": VARIABLE_REFERENCE("timeLeft"), + "%1$S": VARIABLE_REFERENCE("transfer"), + "%2$S": COPY(source, "infiniteRate"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-status-no-rate"), + value=REPLACE( + source, + "statusFormatNoRate", + { + "%2$S": VARIABLE_REFERENCE("timeLeft"), + "%1$S": VARIABLE_REFERENCE("transfer"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-bytes"), value=COPY(source, "bytes") + ), + FTL.Message( + id=FTL.Identifier("download-utils-kilobyte"), + value=COPY(source, "kilobyte"), + ), + FTL.Message( + id=FTL.Identifier("download-utils-megabyte"), + value=COPY(source, "megabyte"), + ), + FTL.Message( + id=FTL.Identifier("download-utils-gigabyte"), + value=COPY(source, "gigabyte"), + ), + FTL.Message( + id=FTL.Identifier("download-utils-transfer-same-units"), + value=REPLACE( + source, + "transferSameUnits2", + { + "%1$S": VARIABLE_REFERENCE("progress"), + "%2$S": VARIABLE_REFERENCE("total"), + "%3$S": VARIABLE_REFERENCE("totalUnits"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-transfer-diff-units"), + value=REPLACE( + source, + "transferDiffUnits2", + { + "%1$S": VARIABLE_REFERENCE("progress"), + "%2$S": VARIABLE_REFERENCE("progressUnits"), + "%3$S": VARIABLE_REFERENCE("total"), + "%4$S": VARIABLE_REFERENCE("totalUnits"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-transfer-no-total"), + value=REPLACE( + source, + "transferNoTotal2", + { + "%1$S": VARIABLE_REFERENCE("progress"), + "%2$S": VARIABLE_REFERENCE("progressUnits"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-time-pair"), + value=REPLACE( + source, + "timePair3", + { + "%1$S": VARIABLE_REFERENCE("time"), + "%2$S": VARIABLE_REFERENCE("unit"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-time-left-single"), + value=REPLACE( + source, "timeLeftSingle3", {"%1$S": VARIABLE_REFERENCE("time")} + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-time-left-double"), + value=REPLACE( + source, + "timeLeftDouble3", + { + "%1$S": VARIABLE_REFERENCE("time1"), + "%2$S": VARIABLE_REFERENCE("time2"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-time-few-seconds"), + value=COPY(source, "timeFewSeconds2"), + ), + FTL.Message( + id=FTL.Identifier("download-utils-time-unknown"), + value=COPY(source, "timeUnknown2"), + ), + FTL.Message( + id=FTL.Identifier("download-utils-done-scheme"), + value=REPLACE( + source, "doneScheme2", {"%1$S": VARIABLE_REFERENCE("scheme")} + ), + ), + FTL.Message( + id=FTL.Identifier("download-utils-done-file-scheme"), + value=COPY(source, "doneFileScheme"), + ), + FTL.Message( + id=FTL.Identifier("download-utils-yesterday"), + value=COPY(source, "yesterday"), + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1791178_nsContextMenu_saveHelper.py b/python/l10n/fluent_migrations/bug_1791178_nsContextMenu_saveHelper.py new file mode 100644 index 0000000000..d12533be9a --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1791178_nsContextMenu_saveHelper.py @@ -0,0 +1,39 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import VARIABLE_REFERENCE +from fluent.migrate.transforms import COPY, REPLACE + + +def migrate(ctx): + """Bug 1791178 - Convert nsContextMenu saveHelper() localization to Fluent, part {index}.""" + + source = "toolkit/chrome/mozapps/downloads/downloads.properties" + target = "browser/browser/downloads.ftl" + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("downloads-error-alert-title"), + value=COPY(source, "downloadErrorAlertTitle"), + ), + FTL.Message( + id=FTL.Identifier("downloads-error-blocked-by"), + value=REPLACE( + source, + "downloadErrorBlockedBy", + {"%1$S": VARIABLE_REFERENCE("extension")}, + ), + ), + FTL.Message( + id=FTL.Identifier("downloads-error-extension"), + value=COPY(source, "downloadErrorExtension"), + ), + FTL.Message( + id=FTL.Identifier("downloads-error-generic"), + value=COPY(source, "downloadErrorGeneric"), + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1793572_webrtc.py b/python/l10n/fluent_migrations/bug_1793572_webrtc.py new file mode 100644 index 0000000000..eb07f939a8 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1793572_webrtc.py @@ -0,0 +1,771 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import TERM_REFERENCE, VARIABLE_REFERENCE +from fluent.migrate.transforms import ( + COPY, + COPY_PATTERN, + PLURALS, + REPLACE, + REPLACE_IN_TEXT, +) + + +def migrate(ctx): + """Bug 1793572 - Convert WebRTC strings to Fluent, part {index}.""" + + source = "browser/chrome/browser/webrtcIndicator.properties" + browser = "browser/chrome/browser/browser.properties" + browser_ftl = "browser/browser/browser.ftl" + target = "browser/browser/webrtcIndicator.ftl" + + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("webrtc-indicator-window"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("title"), + value=COPY_PATTERN(target, "webrtc-indicator-title"), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-sharing-camera-and-microphone"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=COPY( + source, "webrtcIndicator.sharingCameraAndMicrophone.tooltip" + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-sharing-camera"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=COPY(source, "webrtcIndicator.sharingCamera.tooltip"), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-sharing-microphone"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=COPY(source, "webrtcIndicator.sharingMicrophone.tooltip"), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-sharing-application"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=COPY( + source, "webrtcIndicator.sharingApplication.tooltip" + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-sharing-screen"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=COPY(source, "webrtcIndicator.sharingScreen.tooltip"), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-sharing-window"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=COPY(source, "webrtcIndicator.sharingWindow.tooltip"), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-sharing-browser"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("tooltiptext"), + value=COPY(source, "webrtcIndicator.sharingBrowser.tooltip"), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-control-sharing"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(source, "webrtcIndicator.controlSharing.menuitem"), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-control-sharing-on"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + source, + "webrtcIndicator.controlSharingOn.menuitem", + {"%1$S": VARIABLE_REFERENCE("streamTitle")}, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-sharing-camera-with"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + source, + "webrtcIndicator.sharingCameraWith.menuitem", + {"%1$S": VARIABLE_REFERENCE("streamTitle")}, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-sharing-microphone-with"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + source, + "webrtcIndicator.sharingMicrophoneWith.menuitem", + {"%1$S": VARIABLE_REFERENCE("streamTitle")}, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-sharing-application-with"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + source, + "webrtcIndicator.sharingApplicationWith.menuitem", + {"%1$S": VARIABLE_REFERENCE("streamTitle")}, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-sharing-screen-with"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + source, + "webrtcIndicator.sharingScreenWith.menuitem", + {"%1$S": VARIABLE_REFERENCE("streamTitle")}, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-sharing-window-with"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + source, + "webrtcIndicator.sharingWindowWith.menuitem", + {"%1$S": VARIABLE_REFERENCE("streamTitle")}, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-indicator-menuitem-sharing-browser-with"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=REPLACE( + source, + "webrtcIndicator.sharingBrowserWith.menuitem", + {"%1$S": VARIABLE_REFERENCE("streamTitle")}, + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-indicator-menuitem-sharing-camera-with-n-tabs" + ), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "webrtcIndicator.sharingCameraWithNTabs.menuitem", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-indicator-menuitem-sharing-microphone-with-n-tabs" + ), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "webrtcIndicator.sharingMicrophoneWithNTabs.menuitem", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-indicator-menuitem-sharing-application-with-n-tabs" + ), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "webrtcIndicator.sharingApplicationWithNTabs.menuitem", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-indicator-menuitem-sharing-screen-with-n-tabs" + ), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "webrtcIndicator.sharingScreenWithNTabs.menuitem", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-indicator-menuitem-sharing-window-with-n-tabs" + ), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "webrtcIndicator.sharingWindowWithNTabs.menuitem", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-indicator-menuitem-sharing-browser-with-n-tabs" + ), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=PLURALS( + source, + "webrtcIndicator.sharingBrowserWithNTabs.menuitem", + VARIABLE_REFERENCE("tabCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + {"#1": VARIABLE_REFERENCE("tabCount")}, + ), + ), + ) + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-item-camera"), + value=REPLACE( + browser, + "getUserMedia.sharingMenuCamera", + { + "%1$S (": FTL.TextElement(""), + "%1$S(": FTL.TextElement(""), + ")": FTL.TextElement(""), + ")": FTL.TextElement(""), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-item-microphone"), + value=REPLACE( + browser, + "getUserMedia.sharingMenuMicrophone", + { + "%1$S (": FTL.TextElement(""), + "%1$S(": FTL.TextElement(""), + ")": FTL.TextElement(""), + ")": FTL.TextElement(""), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-item-audio-capture"), + value=REPLACE( + browser, + "getUserMedia.sharingMenuAudioCapture", + { + "%1$S (": FTL.TextElement(""), + "%1$S(": FTL.TextElement(""), + ")": FTL.TextElement(""), + ")": FTL.TextElement(""), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-item-application"), + value=REPLACE( + browser, + "getUserMedia.sharingMenuApplication", + { + "%1$S (": FTL.TextElement(""), + "%1$S(": FTL.TextElement(""), + ")": FTL.TextElement(""), + ")": FTL.TextElement(""), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-item-screen"), + value=REPLACE( + browser, + "getUserMedia.sharingMenuScreen", + { + "%1$S (": FTL.TextElement(""), + "%1$S(": FTL.TextElement(""), + ")": FTL.TextElement(""), + ")": FTL.TextElement(""), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-item-window"), + value=REPLACE( + browser, + "getUserMedia.sharingMenuWindow", + { + "%1$S (": FTL.TextElement(""), + "%1$S(": FTL.TextElement(""), + ")": FTL.TextElement(""), + ")": FTL.TextElement(""), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-item-browser"), + value=REPLACE( + browser, + "getUserMedia.sharingMenuBrowser", + { + "%1$S (": FTL.TextElement(""), + "%1$S(": FTL.TextElement(""), + ")": FTL.TextElement(""), + ")": FTL.TextElement(""), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-sharing-menuitem-unknown-host"), + value=COPY(browser, "getUserMedia.sharingMenuUnknownHost"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-sharing-menuitem"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=FTL.Pattern( + [FTL.TextElement("{ $origin } ({ $itemList })")] + ), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-sharing-menu"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser, "getUserMedia.sharingMenu.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser, "getUserMedia.sharingMenu.accesskey"), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-camera"), + value=REPLACE( + browser, + "getUserMedia.shareCamera3.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-microphone"), + value=REPLACE( + browser, + "getUserMedia.shareMicrophone3.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-screen"), + value=REPLACE( + browser, + "getUserMedia.shareScreen4.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-camera-and-microphone"), + value=REPLACE( + browser, + "getUserMedia.shareCameraAndMicrophone3.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-camera-and-audio-capture"), + value=REPLACE( + browser, + "getUserMedia.shareCameraAndAudioCapture3.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-screen-and-microphone"), + value=REPLACE( + browser, + "getUserMedia.shareScreenAndMicrophone4.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-screen-and-audio-capture"), + value=REPLACE( + browser, + "getUserMedia.shareScreenAndAudioCapture4.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-audio-capture"), + value=REPLACE( + browser, + "getUserMedia.shareAudioCapture3.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-speaker"), + value=REPLACE( + browser, + "selectAudioOutput.shareSpeaker.message", + {"%1$S": VARIABLE_REFERENCE("origin")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-camera-unsafe-delegation"), + value=REPLACE( + browser, + "getUserMedia.shareCameraUnsafeDelegation2.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-microphone-unsafe-delegations"), + value=REPLACE( + browser, + "getUserMedia.shareMicrophoneUnsafeDelegations2.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-screen-unsafe-delegation"), + value=REPLACE( + browser, + "getUserMedia.shareScreenUnsafeDelegation2.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-allow-share-camera-and-microphone-unsafe-delegation" + ), + value=REPLACE( + browser, + "getUserMedia.shareCameraAndMicrophoneUnsafeDelegation2.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-allow-share-camera-and-audio-capture-unsafe-delegation" + ), + value=REPLACE( + browser, + "getUserMedia.shareCameraAndAudioCaptureUnsafeDelegation2.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-allow-share-screen-and-microphone-unsafe-delegation" + ), + value=REPLACE( + browser, + "getUserMedia.shareScreenAndMicrophoneUnsafeDelegation2.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier( + "webrtc-allow-share-screen-and-audio-capture-unsafe-delegation" + ), + value=REPLACE( + browser, + "getUserMedia.shareScreenAndAudioCaptureUnsafeDelegation2.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-speaker-unsafe-delegation"), + value=REPLACE( + browser, + "selectAudioOutput.shareSpeakerUnsafeDelegation.message", + { + "%1$S": VARIABLE_REFERENCE("origin"), + "%2$S": VARIABLE_REFERENCE("thirdParty"), + }, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-share-screen-warning"), + value=COPY(browser, "getUserMedia.shareScreenWarning2.message"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-share-browser-warning"), + value=REPLACE( + browser, + "getUserMedia.shareFirefoxWarning2.message", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-share-screen-learn-more"), + value=COPY(browser, "getUserMedia.shareScreen.learnMoreLabel"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-pick-window-or-screen"), + value=COPY(browser, "getUserMedia.pickWindowOrScreen.label"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-share-entire-screen"), + value=COPY(browser, "getUserMedia.shareEntireScreen.label"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-share-pipe-wire-portal"), + value=COPY(browser, "getUserMedia.sharePipeWirePortal.label"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-share-monitor"), + value=REPLACE( + browser, + "getUserMedia.shareMonitor.label", + {"%1$S": VARIABLE_REFERENCE("monitorIndex")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-share-application"), + value=PLURALS( + browser, + "getUserMedia.shareApplicationWindowCount.label", + VARIABLE_REFERENCE("windowCount"), + foreach=lambda n: REPLACE_IN_TEXT( + n, + { + "#1": VARIABLE_REFERENCE("appName"), + "#2": VARIABLE_REFERENCE("windowCount"), + }, + ), + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-action-allow"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser, "getUserMedia.allow.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser, "getUserMedia.allow.accesskey"), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-action-block"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY_PATTERN( + browser_ftl, "popup-screen-sharing-block.label" + ), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY_PATTERN( + browser_ftl, "popup-screen-sharing-block.accesskey" + ), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-action-always-block"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY_PATTERN( + browser_ftl, "popup-screen-sharing-always-block.label" + ), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY_PATTERN( + browser_ftl, + "popup-screen-sharing-always-block.accesskey", + ), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-action-not-now"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser, "getUserMedia.notNow.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY(browser, "getUserMedia.notNow.accesskey"), + ), + ], + ), + FTL.Message( + id=FTL.Identifier("webrtc-remember-allow-checkbox"), + value=COPY(browser, "getUserMedia.remember"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-mute-notifications-checkbox"), + value=COPY_PATTERN(browser_ftl, "popup-mute-notifications-checkbox"), + ), + FTL.Message( + id=FTL.Identifier("webrtc-reason-for-no-permanent-allow-screen"), + value=REPLACE( + browser, + "getUserMedia.reasonForNoPermanentAllow.screen3", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-reason-for-no-permanent-allow-audio"), + value=REPLACE( + browser, + "getUserMedia.reasonForNoPermanentAllow.audio", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + FTL.Message( + id=FTL.Identifier("webrtc-reason-for-no-permanent-allow-insecure"), + value=REPLACE( + browser, + "getUserMedia.reasonForNoPermanentAllow.insecure", + {"%1$S": TERM_REFERENCE("brand-short-name")}, + ), + ), + ], + ) + + ctx.add_transforms( + browser_ftl, + browser_ftl, + [ + FTL.Message( + id=FTL.Identifier("popup-select-window-or-screen"), + attributes=[ + FTL.Attribute( + id=FTL.Identifier("label"), + value=COPY(browser, "getUserMedia.selectWindowOrScreen2.label"), + ), + FTL.Attribute( + id=FTL.Identifier("accesskey"), + value=COPY( + browser, "getUserMedia.selectWindowOrScreen2.accesskey" + ), + ), + ], + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1801035_places_window_no_size.py b/python/l10n/fluent_migrations/bug_1801035_places_window_no_size.py new file mode 100644 index 0000000000..072d22cdd8 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1801035_places_window_no_size.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 1801035 - Stop localizing places window style, part {index}.""" + + ctx.add_transforms( + "browser/browser/places.ftl", + "browser/browser/places.ftl", + transforms_from( + """ +places-library3 = + .title = {{COPY_PATTERN(from_path, "places-library2.title")}} + """, + from_path="browser/browser/places.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1801915_confirmationHints.py b/python/l10n/fluent_migrations/bug_1801915_confirmationHints.py new file mode 100644 index 0000000000..69b9cff67d --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1801915_confirmationHints.py @@ -0,0 +1,29 @@ +# coding=utf8 + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.helpers import transforms_from +from fluent.migrate.helpers import VARIABLE_REFERENCE +from fluent.migrate import COPY, REPLACE + + +def migrate(ctx): + """Bug 1801915 - Migrate confirmation Hints to Fluent""" + ctx.add_transforms( + "browser/browser/confirmationHints.ftl", + "browser/browser/confirmationHints.ftl", + transforms_from( + """ +confirmation-hint-breakage-report-sent = { COPY(from_path, "confirmationHint.breakageReport.label") } +confirmation-hint-login-removed = { COPY(from_path, "confirmationHint.loginRemoved.label") } +confirmation-hint-page-bookmarked = { COPY(from_path, "confirmationHint.pageBookmarked2.label") } +confirmation-hint-password-saved = { COPY(from_path, "confirmationHint.passwordSaved.label") } +confirmation-hint-pin-tab = { COPY(from_path, "confirmationHint.pinTab.label") } +confirmation-hint-pin-tab-description = { COPY(from_path, "confirmationHint.pinTab.description") } +confirmation-hint-send-to-device = { COPY(from_path, "confirmationHint.sendToDevice.label") } +""", + from_path="browser/chrome/browser/browser.properties", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1802128_langpack_defines.py b/python/l10n/fluent_migrations/bug_1802128_langpack_defines.py new file mode 100644 index 0000000000..d1755307bd --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1802128_langpack_defines.py @@ -0,0 +1,43 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import fluent.syntax.ast as FTL +from fluent.migrate.transforms import COPY, LegacySource, Transform + + +class CONTRIB(LegacySource): + """Custom transform converting a list of contributors from the old RDF based + list of entries into a comma delimited list. + + Based on code from python/mozbuild/mozbuild/action/langpack_manifest.py + """ + + def __call__(self, ctx): + element: FTL.TextElement = super(CONTRIB, self).__call__(ctx) + str: str = element.value.replace("<em:contributor>", "") + tokens = str.split("</em:contributor>") + tokens = map(lambda t: t.strip(), tokens) + tokens = filter(lambda t: t != "", tokens) + element.value = ", ".join(tokens) + return Transform.pattern_of(element) + + +def migrate(ctx): + """Bug 1802128 - Convert langpack defines.inc to Fluent, part {index}.""" + + browser = "browser/defines.inc" + target = "browser/langpack-metadata.ftl" + ctx.add_transforms( + target, + target, + [ + FTL.Message( + id=FTL.Identifier("langpack-creator"), + value=COPY(browser, "MOZ_LANGPACK_CREATOR"), + ), + FTL.Message( + id=FTL.Identifier("langpack-contributors"), + value=CONTRIB(browser, "MOZ_LANGPACK_CONTRIBUTORS"), + ), + ], + ) diff --git a/python/l10n/fluent_migrations/bug_1803010_downloads.py b/python/l10n/fluent_migrations/bug_1803010_downloads.py new file mode 100644 index 0000000000..bfea61b564 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1803010_downloads.py @@ -0,0 +1,30 @@ +# 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 1803010 - Use .value consistently in downloads.ftl, part {index}.""" + + for string in [ + "downloading-file-opens-in-hours-and-minutes", + "downloading-file-opens-in-minutes", + "downloading-file-opens-in-minutes-and-seconds", + "downloading-file-opens-in-seconds", + "downloading-file-opens-in-some-time", + ]: + ctx.add_transforms( + "browser/browser/downloads.ftl", + "browser/browser/downloads.ftl", + transforms_from( + """ +{0}-2 = + .value = {{COPY_PATTERN(from_path, "{0}")}} + """.format( + string + ), + from_path="browser/browser/downloads.ftl", + ), + ) diff --git a/python/l10n/fluent_migrations/bug_1805319_webrtc_indicator_typo.py b/python/l10n/fluent_migrations/bug_1805319_webrtc_indicator_typo.py new file mode 100644 index 0000000000..e37ad5da68 --- /dev/null +++ b/python/l10n/fluent_migrations/bug_1805319_webrtc_indicator_typo.py @@ -0,0 +1,23 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from fluent.migrate import COPY_PATTERN +import fluent.syntax.ast as FTL + + +def migrate(ctx): + """Bug 1805319 - Fix typo in webrtc indicator message id, part {index}.""" + + path = "browser/browser/webrtcIndicator.ftl" + ctx.add_transforms( + path, + path, + [ + FTL.Message( + id=FTL.Identifier("webrtc-allow-share-microphone-unsafe-delegation"), + value=COPY_PATTERN( + path, "webrtc-allow-share-microphone-unsafe-delegations" + ), + ), + ], + ) diff --git a/python/l10n/mozxchannel/__init__.py b/python/l10n/mozxchannel/__init__.py new file mode 100644 index 0000000000..66ee3966ca --- /dev/null +++ b/python/l10n/mozxchannel/__init__.py @@ -0,0 +1,150 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this, +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import shutil +from collections import defaultdict +from dataclasses import dataclass, field +from datetime import datetime +from pathlib import Path + +import hglib +from compare_locales import merge +from mozpack import path as mozpath + +from . import projectconfig, source + + +def get_default_config(topsrcdir, strings_path): + assert isinstance(topsrcdir, Path) + assert isinstance(strings_path, Path) + return { + "strings": { + "path": strings_path, + "url": "https://hg.mozilla.org/l10n/gecko-strings-quarantine/", + "heads": {"default": "default"}, + "update_on_pull": True, + "push_url": "ssh://hg.mozilla.org/l10n/gecko-strings-quarantine/", + }, + "source": { + "mozilla-unified": { + "path": topsrcdir, + "url": "https://hg.mozilla.org/mozilla-unified/", + "heads": { + # This list of repositories is ordered, starting with the + # one with the most recent content (central) to the oldest + # (ESR). In case two ESR versions are supported, the oldest + # ESR goes last (e.g. esr78 goes after esr91). + "central": "mozilla-central", + "beta": "releases/mozilla-beta", + "release": "releases/mozilla-release", + "esr102": "releases/mozilla-esr102", + }, + "config_files": [ + "browser/locales/l10n.toml", + "mobile/android/locales/l10n.toml", + ], + }, + }, + } + + +@dataclass +class TargetRevs: + target: bytes = None + revs: list = field(default_factory=list) + + +@dataclass +class CommitRev: + repo: str + rev: bytes + + @property + def message(self): + return ( + f"X-Channel-Repo: {self.repo}\n" + f'X-Channel-Revision: {self.rev.decode("ascii")}' + ) + + +class CrossChannelCreator: + def __init__(self, config): + self.config = config + self.strings_path = config["strings"]["path"] + self.message = ( + f"cross-channel content for {datetime.utcnow().strftime('%Y-%m-%d %H:%M')}" + ) + + def create_content(self): + self.prune_target() + revs = [] + for repo_name, repo_config in self.config["source"].items(): + with hglib.open(repo_config["path"]) as repo: + revs.extend(self.create_for_repo(repo, repo_name, repo_config)) + self.commit(revs) + return 0 + + def prune_target(self): + for leaf in self.config["strings"]["path"].iterdir(): + if leaf.name == ".hg": + continue + shutil.rmtree(leaf) + + def create_for_repo(self, repo, repo_name, repo_config): + print(f"Processing {repo_name} in {repo_config['path']}") + source_target_revs = defaultdict(TargetRevs) + revs_for_commit = [] + parse_kwargs = { + "env": {"l10n_base": str(self.strings_path.parent)}, + "ignore_missing_includes": True, + } + for head, head_name in repo_config["heads"].items(): + print(f"Gathering files for {head}") + rev = repo.log(revrange=head)[0].node + revs_for_commit.append(CommitRev(head_name, rev)) + p = source.HgTOMLParser(repo, rev) + project_configs = [] + for config_file in repo_config["config_files"]: + project_configs.append(p.parse(config_file, **parse_kwargs)) + project_configs[-1].set_locales(["en-US"], deep=True) + hgfiles = source.HGFiles(repo, rev, project_configs) + for targetpath, refpath, _, _ in hgfiles: + source_target_revs[refpath].revs.append(rev) + source_target_revs[refpath].target = targetpath + root = repo.root() + print(f"Writing {repo_name} content to target") + for refpath, targetrevs in source_target_revs.items(): + local_ref = mozpath.relpath(refpath, root) + content = self.get_content(local_ref, repo, targetrevs.revs) + target_dir = mozpath.dirname(targetrevs.target) + if not os.path.isdir(target_dir): + os.makedirs(target_dir) + with open(targetrevs.target, "wb") as fh: + fh.write(content) + return revs_for_commit + + def commit(self, revs): + message = self.message + "\n\n" + if "TASK_ID" in os.environ: + message += f"X-Task-ID: {os.environ['TASK_ID']}\n\n" + message += "\n".join(rev.message for rev in revs) + with hglib.open(self.strings_path) as repo: + repo.commit(message=message, addremove=True) + + def get_content(self, local_ref, repo, revs): + if local_ref.endswith(b".toml"): + return self.get_config_content(local_ref, repo, revs) + if len(revs) < 2: + return repo.cat([b"path:" + local_ref], rev=revs[0]) + contents = [repo.cat([b"path:" + local_ref], rev=rev) for rev in revs] + try: + return merge.merge_channels(local_ref.decode("utf-8"), contents) + except merge.MergeNotSupportedError: + return contents[0] + + def get_config_content(self, local_ref, repo, revs): + # We don't support merging toml files + content = repo.cat([b"path:" + local_ref], rev=revs[0]) + return projectconfig.process_config(content) diff --git a/python/l10n/mozxchannel/projectconfig.py b/python/l10n/mozxchannel/projectconfig.py new file mode 100644 index 0000000000..23d8120a3c --- /dev/null +++ b/python/l10n/mozxchannel/projectconfig.py @@ -0,0 +1,77 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this, +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import re + +from compare_locales import mozpath + +# The local path where we write the config files to +TARGET_PATH = b"_configs" + + +def process_config(toml_content): + """Process TOML configuration content to match the l10n setup for + the reference localization, return target_path and content. + + The code adjusts basepath, [[paths]], and [[includes]] + """ + # adjust basepath in content. '.' works in practice, also in theory? + new_base = mozpath.relpath(b".", TARGET_PATH) + if not new_base: + new_base = b"." # relpath to '.' is '', sadly + base_line = b'\nbasepath = "%s"' % new_base + content1 = re.sub(br"^\s*basepath\s*=\s*.+", base_line, toml_content, flags=re.M) + + # process [[paths]] + start = 0 + content2 = b"" + for m in re.finditer( + br"\[\[\s*paths\s*\]\].+?(?=\[|\Z)", content1, re.M | re.DOTALL + ): + content2 += content1[start : m.start()] + path_content = m.group() + l10n_line = re.search(br"^\s*l10n\s*=.*$", path_content, flags=re.M).group() + # remove variable expansions + new_reference = re.sub(br"{\s*\S+\s*}", b"", l10n_line) + # make the l10n a reference line + new_reference = re.sub(br"^(\s*)l10n(\s*=)", br"\1reference\2", new_reference) + content2 += re.sub( + br"^\s*reference\s*=.*$", new_reference, path_content, flags=re.M + ) + start = m.end() + content2 += content1[start:] + + start = 0 + content3 = b"" + for m in re.finditer( + br"\[\[\s*includes\s*\]\].+?(?=\[|\Z)", content2, re.M | re.DOTALL + ): + content3 += content2[start : m.start()] + include_content = m.group() + m_ = re.search(br'^\s*path = "(.+?)"', include_content, flags=re.M) + content3 += ( + include_content[: m_.start(1)] + + generate_filename(m_.group(1)) + + include_content[m_.end(1) :] + ) + start = m.end() + content3 += content2[start:] + + return content3 + + +def generate_filename(path): + segs = path.split(b"/") + # strip /locales/ from filename + segs = [seg for seg in segs if seg != b"locales"] + # strip variables from filename + segs = [seg for seg in segs if not seg.startswith(b"{") and not seg.endswith(b"}")] + if segs[-1] == b"l10n.toml": + segs.pop() + segs[-1] += b".toml" + outpath = b"-".join(segs) + if TARGET_PATH != b".": + # prepend the target path, if it's not '.' + outpath = mozpath.join(TARGET_PATH, outpath) + return outpath diff --git a/python/l10n/mozxchannel/source.py b/python/l10n/mozxchannel/source.py new file mode 100644 index 0000000000..683a7b0640 --- /dev/null +++ b/python/l10n/mozxchannel/source.py @@ -0,0 +1,84 @@ +# 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 pytoml as toml +from compare_locales import mozpath, paths +from compare_locales.paths.matcher import expand + +from .projectconfig import generate_filename + + +class HGFiles(paths.ProjectFiles): + def __init__(self, repo, rev, projects): + self.repo = repo + self.ctx = repo[rev] + self.root = repo.root() + self.manifest = None + self.configs_map = {} + # get paths for our TOML files + for p in projects: + all_configpaths = { + mozpath.abspath(c.path).encode("utf-8") for c in p.configs + } + for refpath in all_configpaths: + local_path = mozpath.relpath(refpath, self.root) + if local_path not in self.ctx: + print("ignoring", refpath) + continue + targetpath = b"/".join( + ( + expand(None, "{l10n_base}", p.environ).encode("utf-8"), + b"en-US", + generate_filename(local_path), + ) + ) + self.configs_map[refpath] = targetpath + super(HGFiles, self).__init__("en-US", projects) + for m in self.matchers: + m["l10n"].encoding = "utf-8" + if "reference" in m: + m["reference"].encoding = "utf-8" + if self.exclude: + for m in self.exclude.matchers: + m["l10n"].encoding = "utf-8" + if "reference" in m: + m["reference"].encoding = "utf-8" + + def _files(self, matcher): + for f in self.ctx.manifest(): + f = mozpath.join(self.root, f) + if matcher.match(f): + yield f + + def __iter__(self): + for t in super(HGFiles, self).__iter__(): + yield t + for refpath, targetpath in self.configs_map.items(): + yield targetpath, refpath, None, set() + + def match(self, path): + m = super(HGFiles, self).match(path) + if m: + return m + for refpath, targetpath in self.configs_map.items(): + if path in [refpath, targetpath]: + return targetpath, refpath, None, set() + + +class HgTOMLParser(paths.TOMLParser): + "subclass to load from our hg context" + + def __init__(self, repo, rev): + self.repo = repo + self.rev = rev + self.root = repo.root().decode("utf-8") + + def load(self, parse_ctx): + try: + path = parse_ctx.path + local_path = "path:" + mozpath.relpath(path, self.root) + data = self.repo.cat(files=[local_path.encode("utf-8")], rev=self.rev) + except Exception: + raise paths.ConfigNotFound(parse_ctx.path) + parse_ctx.data = toml.loads(data, filename=parse_ctx.path) diff --git a/python/l10n/test_fluent_migrations/__init__.py b/python/l10n/test_fluent_migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/python/l10n/test_fluent_migrations/__init__.py diff --git a/python/l10n/test_fluent_migrations/fmt.py b/python/l10n/test_fluent_migrations/fmt.py new file mode 100644 index 0000000000..150a942e78 --- /dev/null +++ b/python/l10n/test_fluent_migrations/fmt.py @@ -0,0 +1,188 @@ +import codecs +import logging +import os +import re +import shutil +import sys +from difflib import unified_diff + +import hglib +import mozpack.path as mozpath +from compare_locales.merge import merge_channels +from compare_locales.paths.configparser import TOMLParser +from compare_locales.paths.files import ProjectFiles +from fluent.migrate import validator +from fluent.syntax import FluentParser, FluentSerializer +from mach.util import get_state_dir + + +def inspect_migration(path): + """Validate recipe and extract some metadata.""" + return validator.Validator.validate(path) + + +def prepare_object_dir(cmd): + """Prepare object dir to have an up-to-date clone of gecko-strings. + + We run this once per mach invocation, for all tested migrations. + """ + obj_dir = mozpath.join(cmd.topobjdir, "python", "l10n") + if not os.path.exists(obj_dir): + os.makedirs(obj_dir) + state_dir = get_state_dir() + if os.path.exists(mozpath.join(state_dir, "gecko-strings")): + cmd.run_process( + ["hg", "pull", "-u"], cwd=mozpath.join(state_dir, "gecko-strings") + ) + else: + cmd.run_process( + ["hg", "clone", "https://hg.mozilla.org/l10n/gecko-strings"], + cwd=state_dir, + ) + return obj_dir + + +def diff_resources(left_path, right_path): + parser = FluentParser(with_spans=False) + serializer = FluentSerializer(with_junk=True) + lines = [] + for p in (left_path, right_path): + with codecs.open(p, encoding="utf-8") as fh: + res = parser.parse(fh.read()) + lines.append(serializer.serialize(res).splitlines(True)) + sys.stdout.writelines( + chunk for chunk in unified_diff(lines[0], lines[1], left_path, right_path) + ) + + +def test_migration(cmd, obj_dir, to_test, references): + """Test the given recipe. + + This creates a workdir by l10n-merging gecko-strings and the m-c source, + to mimmic gecko-strings after the patch to test landed. + It then runs the recipe with a gecko-strings clone as localization, both + dry and wet. + It inspects the generated commits, and shows a diff between the merged + reference and the generated content. + The diff is intended to be visually inspected. Some changes might be + expected, in particular when formatting of the en-US strings is different. + """ + rv = 0 + migration_name = os.path.splitext(os.path.split(to_test)[1])[0] + work_dir = mozpath.join(obj_dir, migration_name) + + paths = os.path.normpath(to_test).split(os.sep) + # Migration modules should be in a sub-folder of l10n. + migration_module = ( + ".".join(paths[paths.index("l10n") + 1 : -1]) + "." + migration_name + ) + + if os.path.exists(work_dir): + shutil.rmtree(work_dir) + os.makedirs(mozpath.join(work_dir, "reference")) + l10n_toml = mozpath.join( + cmd.topsrcdir, cmd.substs["MOZ_BUILD_APP"], "locales", "l10n.toml" + ) + pc = TOMLParser().parse(l10n_toml, env={"l10n_base": work_dir}) + pc.set_locales(["reference"]) + files = ProjectFiles("reference", [pc]) + for ref in references: + if ref != mozpath.normpath(ref): + cmd.log( + logging.ERROR, + "fluent-migration-test", + { + "file": to_test, + "ref": ref, + }, + 'Reference path "{ref}" needs to be normalized for {file}', + ) + rv = 1 + continue + full_ref = mozpath.join(work_dir, "reference", ref) + m = files.match(full_ref) + if m is None: + raise ValueError("Bad reference path: " + ref) + m_c_path = m[1] + g_s_path = mozpath.join(work_dir, "gecko-strings", ref) + resources = [ + b"" if not os.path.exists(f) else open(f, "rb").read() + for f in (g_s_path, m_c_path) + ] + ref_dir = os.path.dirname(full_ref) + if not os.path.exists(ref_dir): + os.makedirs(ref_dir) + open(full_ref, "wb").write(merge_channels(ref, resources)) + client = hglib.clone( + source=mozpath.join(get_state_dir(), "gecko-strings"), + dest=mozpath.join(work_dir, "en-US"), + ) + client.open() + old_tip = client.tip().node + run_migration = [ + cmd._virtualenv_manager.python_path, + "-m", + "fluent.migrate.tool", + "--lang", + "en-US", + "--reference-dir", + mozpath.join(work_dir, "reference"), + "--localization-dir", + mozpath.join(work_dir, "en-US"), + "--dry-run", + migration_module, + ] + cmd.run_process( + run_migration, + cwd=work_dir, + line_handler=print, + ) + # drop --dry-run + run_migration.pop(-2) + cmd.run_process( + run_migration, + cwd=work_dir, + line_handler=print, + ) + tip = client.tip().node + if old_tip == tip: + cmd.log( + logging.WARN, + "fluent-migration-test", + { + "file": to_test, + }, + "No migration applied for {file}", + ) + return rv + for ref in references: + diff_resources( + mozpath.join(work_dir, "reference", ref), + mozpath.join(work_dir, "en-US", ref), + ) + messages = [ + l.desc.decode("utf-8") for l in client.log(b"::%s - ::%s" % (tip, old_tip)) + ] + bug = re.search("[0-9]{5,}", migration_name) + # Just check first message for bug number, they're all following the same pattern + if bug is None or bug.group() not in messages[0]: + rv = 1 + cmd.log( + logging.ERROR, + "fluent-migration-test", + { + "file": to_test, + }, + "Missing or wrong bug number for {file}", + ) + if any("part {}".format(n + 1) not in msg for n, msg in enumerate(messages)): + rv = 1 + cmd.log( + logging.ERROR, + "fluent-migration-test", + { + "file": to_test, + }, + 'Commit messages should have "part {{index}}" for {file}', + ) + return rv |