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