From 0b848eae5681d5fe4b88b478ccf9f0cc982ecb46 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 22 Nov 2020 14:57:53 +1000 Subject: [PATCH] update remaining python format strings to Fluent --- ftl/core/browsing.ftl | 32 ++++++++------- ftl/core/card-templates.ftl | 26 ++++++------ ftl/core/importing.ftl | 47 ++++++++++++---------- ftl/qt/about.ftl | 2 +- ftl/qt/addons.ftl | 25 ++++++------ pylib/anki/importing/csvfile.py | 10 ++--- qt/aqt/about.py | 6 +-- qt/aqt/addons.py | 46 ++++++++++----------- qt/aqt/browser.py | 8 ++-- qt/aqt/clayout.py | 6 ++- scripts/BUILD.bazel | 6 +++ scripts/transform-string.py | 71 +++++++++++++++++++++++++++++++++ 12 files changed, 187 insertions(+), 98 deletions(-) create mode 100644 scripts/transform-string.py diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index 82a089255..b359e2012 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -43,7 +43,7 @@ browsing-first-card = First Card browsing-flag = Flag browsing-font = Font: browsing-font-size = Font Size: -browsing-found-as-across-bs = Found %(a)s across %(b)s. +browsing-found-as-across-bs = Found { $part } across { $whole }. browsing-home = Home browsing-ignore-case = Ignore case browsing-in = In: @@ -54,7 +54,7 @@ browsing-line-size = Line Size: browsing-manage-note-types = Manage Note Types... browsing-move-cards = Move Cards browsing-move-cards-to-deck = Move cards to deck: -browsing-nd-names = %(n)d: %(name)s +browsing-nd-names = { $num }: { $name } browsing-new = (new) browsing-new-note-type = New note type: browsing-no-flag = No Flag @@ -102,17 +102,19 @@ browsing-treat-input-as-regular-expression = Treat input as regular expression browsing-type-here-to-search = browsing-whole-collection = Whole Collection browsing-you-must-have-at-least-one = You must have at least one column. -browsing-group = { $count -> - [one] { $count } group - *[other] { $count } groups - } -browsing-note-count = { $count -> - [one] { $count } note - *[other] { $count } notes - } -browsing-note-deleted = { $count -> - [one] { $count } note deleted. - *[other] { $count } notes deleted. - } +browsing-group = + { $count -> + [one] { $count } group + *[other] { $count } groups + } +browsing-note-count = + { $count -> + [one] { $count } note + *[other] { $count } notes + } +browsing-note-deleted = + { $count -> + [one] { $count } note deleted. + *[other] { $count } notes deleted. + } browsing-window-title = Browse ({ $selected } of { $total } cards selected) - diff --git a/ftl/core/card-templates.ftl b/ftl/core/card-templates.ftl index 8b66fc00e..74d235791 100644 --- a/ftl/core/card-templates.ftl +++ b/ftl/core/card-templates.ftl @@ -11,7 +11,7 @@ card-templates-front-preview = Front Preview card-templates-back-preview = Back Preview card-templates-preview-box = Preview card-templates-template-box = Template -card-templates-sample-cloze = This is a {"{{c1::"}sample{"}}"} cloze deletion. +card-templates-sample-cloze = This is a { "{{c1::" }sample{ "}}" } cloze deletion. card-templates-fill-empty = Fill Empty Fields card-templates-night-mode = Night Mode # Add "mobile" class to card preview, so the card appears like it would @@ -30,21 +30,23 @@ card-templates-card-types = Card Types card-templates-card-types-for = Card Types for { $val } card-templates-cloze = Cloze { $val } card-templates-deck-override = Deck Override... -card-templates-delete-the-as-card-type-and = Delete the '%(a)s' card type, and its %(b)s? +card-templates-delete-the-as-card-type-and = Delete the '{ $template }' card type, and its { $cards }? card-templates-enter-deck-to-place-new = Enter deck to place new { $val } cards in, or leave blank: card-templates-enter-new-card-position-1 = Enter new card position (1...{ $val }): card-templates-flip = Flip card-templates-form = Form -card-templates-off = (off) -card-templates-on = (on) +card-templates-off = (off) +card-templates-on = (on) card-templates-remove-card-type = Remove Card Type... card-templates-rename-card-type = Rename Card Type... card-templates-reposition-card-type = Reposition Card Type... -card-templates-card-count = { $count -> - [one] { $count } card - *[other] { $count } cards - } -card-templates-this-will-create-card-proceed = { $count -> - [one] This will create { $count } card. Proceed? - *[other] This will create { $count } cards. Proceed? - } +card-templates-card-count = + { $count -> + [one] { $count } card + *[other] { $count } cards + } +card-templates-this-will-create-card-proceed = + { $count -> + [one] This will create { $count } card. Proceed? + *[other] This will create { $count } cards. Proceed? + } diff --git a/ftl/core/importing.ftl b/ftl/core/importing.ftl index 08c7413da..60ba0a14e 100644 --- a/ftl/core/importing.ftl +++ b/ftl/core/importing.ftl @@ -36,7 +36,7 @@ importing-notes-that-could-not-be-imported = Notes that could not be imported as importing-notes-updated-as-file-had-newer = Notes updated, as file had newer version: { $val } importing-packaged-anki-deckcollection-apkg-colpkg-zip = Packaged Anki Deck/Collection (*.apkg *.colpkg *.zip) importing-pauker-18-lesson-paugz = Pauker 1.8 Lesson (*.pau.gz) -importing-rows-had-num1d-fields-expected-num2d = '%(row)s' had %(num1)d fields, expected %(num2)d +importing-rows-had-num1d-fields-expected-num2d = '{ $row }' had { $found } fields, expected { $expected } importing-selected-file-was-not-in-utf8 = Selected file was not in UTF-8 format. Please see the importing section of the manual. importing-semicolon = Semicolon importing-skipped = Skipped @@ -52,23 +52,28 @@ importing-unable-to-import-from-a-readonly = Unable to import from a read-only f importing-unknown-file-format = Unknown file format. importing-update-existing-notes-when-first-field = Update existing notes when first field matches importing-updated = Updated -importing-note-added = { $count -> - [one] { $count } note added - *[other] { $count } notes added - } -importing-note-imported = { $count -> - [one] { $count } note imported. - *[other] { $count } notes imported. - } -importing-note-unchanged = { $count -> - [one] { $count } note unchanged - *[other] { $count } notes unchanged - } -importing-note-updated = { $count -> - [one] { $count } note updated - *[other] { $count } notes updated - } -importing-processed-media-file = { $count -> - [one] Processed { $count } media file - *[other] Processed { $count } media files - } +importing-note-added = + { $count -> + [one] { $count } note added + *[other] { $count } notes added + } +importing-note-imported = + { $count -> + [one] { $count } note imported. + *[other] { $count } notes imported. + } +importing-note-unchanged = + { $count -> + [one] { $count } note unchanged + *[other] { $count } notes unchanged + } +importing-note-updated = + { $count -> + [one] { $count } note updated + *[other] { $count } notes updated + } +importing-processed-media-file = + { $count -> + [one] Processed { $count } media file + *[other] Processed { $count } media files + } diff --git a/ftl/qt/about.ftl b/ftl/qt/about.ftl index eb048cb64..6fd190638 100644 --- a/ftl/qt/about.ftl +++ b/ftl/qt/about.ftl @@ -7,4 +7,4 @@ about-copy-debug-info = Copy Debug Info about-if-you-have-contributed-and-are = If you have contributed and are not on this list, please get in touch. about-version = Version { $val } about-visit-website = Visit website -about-written-by-damien-elmes-with-patches = Written by Damien Elmes, with patches, translation, testing and design from:

%(cont)s +about-written-by-damien-elmes-with-patches = Written by Damien Elmes, with patches, translation, testing and design from:

{ $cont } diff --git a/ftl/qt/addons.ftl b/ftl/qt/addons.ftl index 89c8588f6..43894125d 100644 --- a/ftl/qt/addons.ftl +++ b/ftl/qt/addons.ftl @@ -19,13 +19,13 @@ addons-code = Code: addons-config = Config addons-configuration = Configuration addons-corrupt-addon-file = Corrupt add-on file. -addons-disabled = (disabled) +addons-disabled = (disabled) addons-disabled2 = (disabled) addons-download-complete-please-restart-anki-to = Download complete. Please restart Anki to apply changes. -addons-downloaded-fnames = Downloaded %(fname)s -addons-downloading-adbd-kb02fkb = Downloading %(a)d/%(b)d (%(kb)0.2fKB)... -addons-error-downloading-ids-errors = Error downloading %(id)s: %(error)s -addons-error-installing-bases-errors = Error installing %(base)s: %(error)s +addons-downloaded-fnames = Downloaded { $fname } +addons-downloading-adbd-kb02fkb = Downloading { $part }/{ $total } ({ $kilobytes }KB)... +addons-error-downloading-ids-errors = Error downloading { $id }: { $error } +addons-error-installing-bases-errors = Error installing { $base }: { $error } addons-get-addons = Get Add-ons... addons-important-as-addons-are-programs-downloaded = Important: As add-ons are programs downloaded from the internet, they are potentially malicious.You should only install add-ons you trust.

Are you sure you want to proceed with the installation of the following Anki add-on(s)?

%(names)s addons-install-addon = Install Add-on @@ -33,12 +33,12 @@ addons-install-addons = Install Add-on(s) addons-install-anki-addon = Install Anki add-on addons-install-from-file = Install from file... addons-installation-complete = Installation complete -addons-installed-names = Installed %(name)s +addons-installed-names = Installed { $name } addons-installed-successfully = Installed successfully. addons-invalid-addon-manifest = Invalid add-on manifest. addons-invalid-code = Invalid code. addons-invalid-code-or-addon-not-available = Invalid code, or add-on not available for your version of Anki. -addons-invalid-configuration = Invalid configuration: +addons-invalid-configuration = Invalid configuration: addons-invalid-configuration-top-level-object-must = Invalid configuration: top level object must be a map addons-no-updates-available = No updates available. addons-one-or-more-errors-occurred = One or more errors occurred: @@ -49,7 +49,7 @@ addons-please-restart-anki-to-complete-the = Please restart Anki to complete addons-please-select-a-single-addon-first = Please select a single add-on first. addons-requires = (requires { $val }) addons-restored-defaults = Restored defaults -addons-the-following-addons-are-incompatible-with = The following add-ons are incompatible with %(name)s and have been disabled: %(found)s +addons-the-following-addons-are-incompatible-with = The following add-ons are incompatible with { $name } and have been disabled: { $found } addons-the-following-addons-have-updates-available = The following add-ons have updates available. Install them now? addons-the-following-conflicting-addons-were-disabled = The following conflicting add-ons were disabled: addons-this-addon-is-not-compatible-with = This add-on is not compatible with your version of Anki. @@ -59,7 +59,8 @@ addons-unable-to-update-or-delete-addon = Unable to update or delete add-on. Ple addons-unknown-error = Unknown error: { $val } addons-view-addon-page = View Add-on Page addons-view-files = View Files -addons-delete-the-numd-selected-addon = { $count -> - [one] Delete the { $count } selected add-on? - *[other] Delete the { $count } selected add-ons? - } +addons-delete-the-numd-selected-addon = + { $count -> + [one] Delete the { $count } selected add-on? + *[other] Delete the { $count } selected add-ons? + } diff --git a/pylib/anki/importing/csvfile.py b/pylib/anki/importing/csvfile.py index c76a15295..eeee98d46 100644 --- a/pylib/anki/importing/csvfile.py +++ b/pylib/anki/importing/csvfile.py @@ -42,13 +42,11 @@ class TextImporter(NoteImporter): if row: log.append( self.col.tr( - TR.IMPORTING_ROWS_HAD_NUM1D_FIELDS_EXPECTED_NUM2D + TR.IMPORTING_ROWS_HAD_NUM1D_FIELDS_EXPECTED_NUM2D, + row=" ".join(row), + found=len(row), + expected=self.numFields, ) - % { - "row": " ".join(row), - "num1": len(row), - "num2": self.numFields, - } ) ignored += 1 continue diff --git a/qt/aqt/about.py b/qt/aqt/about.py index 73205ad85..71eb1591b 100644 --- a/qt/aqt/about.py +++ b/qt/aqt/about.py @@ -202,9 +202,9 @@ def show(mw): ) ) - abouttext += "

" + tr(TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES) % { - "cont": ", ".join(allusers) - } + abouttext += "

" + tr( + TR.ABOUT_WRITTEN_BY_DAMIEN_ELMES_WITH_PATCHES, cont=", ".join(allusers) + ) abouttext += "

" + tr(TR.ABOUT_IF_YOU_HAVE_CONTRIBUTED_AND_ARE) abouttext += "

" + tr(TR.ABOUT_A_BIG_THANKS_TO_ALL_THE) abt.label.setMinimumWidth(800) diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index ef8d18912..ab65e5903 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -275,8 +275,11 @@ class AddonManager: if conflicting: addons = ", ".join(self.addonName(f) for f in conflicting) showInfo( - tr(TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH) - % dict(name=addon.human_name(), found=addons), + tr( + TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH, + name=addon.human_name(), + found=addons, + ), textFormat="plain", ) @@ -306,7 +309,7 @@ class AddonManager: meta = self.addon_meta(dir) name = meta.human_name() if not meta.enabled: - name += tr(TR.ADDONS_DISABLED) + name += " " + tr(TR.ADDONS_DISABLED) return name # Conflict resolution @@ -469,26 +472,24 @@ class AddonManager: result.errmsg, tr(TR.ADDONS_UNKNOWN_ERROR, val=result.errmsg) ) - if mode == "download": # preserve old format strings for i18n - template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS) + if mode == "download": + template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS, id=base, error=msg) else: - template = tr(TR.ADDONS_ERROR_INSTALLING_BASES_ERRORS) + template = tr(TR.ADDONS_ERROR_INSTALLING_BASES_ERRORS, base=base, error=msg) - name = base - - return [template % dict(base=name, id=name, error=msg)] + return [template] def _installationSuccessReport( self, result: InstallOk, base: str, mode: str = "download" ) -> List[str]: - if mode == "download": # preserve old format strings for i18n - template = tr(TR.ADDONS_DOWNLOADED_FNAMES) - else: - template = tr(TR.ADDONS_INSTALLED_NAMES) - name = result.name or base - strings = [template % dict(name=name, fname=name)] + if mode == "download": + template = tr(TR.ADDONS_DOWNLOADED_FNAMES, fname=name) + else: + template = tr(TR.ADDONS_INSTALLED_NAMES, name=name) + + strings = [template] if result.conflicts: strings.append( @@ -1074,13 +1075,12 @@ class DownloaderInstaller(QObject): def _progress_callback(self, up: int, down: int) -> None: self.dl_bytes += down self.mgr.mw.progress.update( - # T: "%(a)d" is the index of the element currently - # downloaded. "%(b)d" is the number of element to download, - # and "%(kb)0.2f" is the number of downloaded - # kilobytes. This lead for example to "Downloading 3/5 - # (27KB)" - label=tr(TR.ADDONS_DOWNLOADING_ADBD_KB02FKB) - % dict(a=len(self.log) + 1, b=len(self.ids), kb=self.dl_bytes / 1024) + label=tr( + TR.ADDONS_DOWNLOADING_ADBD_KB02FKB, + part=len(self.log) + 1, + total=len(self.ids), + kilobytes=self.dl_bytes / 1024, + ) ) def _download_all(self) -> None: @@ -1361,7 +1361,7 @@ class ConfigEditor(QDialog): showInfo(msg) return except Exception as e: - showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + repr(e)) + showInfo(tr(TR.ADDONS_INVALID_CONFIGURATION) + " " + repr(e)) return if not isinstance(new_conf, dict): diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 967af003c..b43d5b1a2 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1352,8 +1352,10 @@ QTableView {{ gridline-color: {grid} }} for c, tmpl in enumerate(nt["tmpls"]): # T: name is a card type name. n it's order in the list of card type. # T: this is shown in browser's filter, when seeing the list of card type of a note type. - name = tr(TR.BROWSING_ND_NAMES) % dict( - n=c + 1, name=self._escapeMenuItem(tmpl["name"]) + name = tr( + TR.BROWSING_ND_NAMES, + num=c + 1, + name=self._escapeMenuItem(tmpl["name"]), ) subm.addItem( name, self._filterFunc("note", nt["name"], "card", str(c + 1)) @@ -1995,7 +1997,7 @@ where id in %s""" notes = sum(len(r[1]) for r in res) part1 = tr(TR.BROWSING_GROUP, count=groups) part2 = tr(TR.BROWSING_NOTE_COUNT, count=notes) - t += tr(TR.BROWSING_FOUND_AS_ACROSS_BS) % dict(a=part1, b=part2) + t += tr(TR.BROWSING_FOUND_AS_ACROSS_BS, part=part1, whole=part2) t += "

    " for val, nids in res: t += ( diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 47c2211ca..6181dc79d 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -564,8 +564,10 @@ class CardLayout(QDialog): template = self.current_template() cards = tr(TR.CARD_TEMPLATES_CARD_COUNT, count=card_cnt) - msg = tr(TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND) % dict( - a=template["name"], b=cards + msg = tr( + TR.CARD_TEMPLATES_DELETE_THE_AS_CARD_TYPE_AND, + template=template["name"], + cards=cards, ) if not askUser(msg): return diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel index 442618eb8..67de5adab 100644 --- a/scripts/BUILD.bazel +++ b/scripts/BUILD.bazel @@ -13,3 +13,9 @@ py_binary( srcs = ["extract-strings.py"], deps = [requirement("fluent-syntax")], ) + +py_binary( + name = "transform-string", + srcs = ["transform-string.py"], + deps = [requirement("fluent-syntax")], +) diff --git a/scripts/transform-string.py b/scripts/transform-string.py new file mode 100644 index 000000000..cc559eb7b --- /dev/null +++ b/scripts/transform-string.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +# -*- coding: UTF-8 -*- + +""" +Tool to apply transform to an ftl string and its translations. +""" + +import os +import json +import glob +from fluent.syntax import parse, serialize +from fluent.syntax.ast import Junk + +template_root = os.environ["BUILD_WORKSPACE_DIRECTORY"] +template_files = glob.glob( + os.path.join(template_root, "ftl", "*", "*.ftl"), recursive=True +) +translation_root = os.path.join(template_root, "..", "anki-i18n") +translation_files = glob.glob( + os.path.join(translation_root, "*", "*", "*", "*.ftl"), recursive=True +) + +target_repls = [ + ["addons-downloaded-fnames", "%(fname)s", "{ $fname }"], + ["addons-downloading-adbd-kb02fkb", "%(a)d", "{ $part }"], + ["addons-downloading-adbd-kb02fkb", "%(b)d", "{ $total }"], + ["addons-downloading-adbd-kb02fkb", "%(kb)0.2f", "{ $kilobytes }"], + ["addons-error-downloading-ids-errors", "%(id)s", "{ $id }"], + ["addons-error-downloading-ids-errors", "%(error)s", "{ $error }"], + ["addons-error-installing-bases-errors", "%(base)s", "{ $base }"], + ["addons-error-installing-bases-errors", "%(error)s", "{ $error }"], + ["addons-important-as-addons-are-programs-downloaded", "%(name)s", "{ $name }"], + ["addons-installed-names", "%(name)s", "{ $name }"], + ["addons-the-following-addons-are-incompatible-with", "%(name)s", "{ $name }"], + ["addons-the-following-addons-are-incompatible-with", "%(found)s", "{ $found }"], + ["about-written-by-damien-elmes-with-patches", "%(cont)s", "{ $cont }"], + ["importing-rows-had-num1d-fields-expected-num2d", "%(row)s", "{ $row }"], + ["importing-rows-had-num1d-fields-expected-num2d", "%(num1)d", "{ $found }"], + ["importing-rows-had-num1d-fields-expected-num2d", "%(num2)d", "{ $expected }"], + ["card-templates-delete-the-as-card-type-and", "%(a)s", "{ $template }"], + ["card-templates-delete-the-as-card-type-and", "%(b)s", "{ $cards }"], + ["browsing-found-as-across-bs", "%(a)s", "{ $part }"], + ["browsing-found-as-across-bs", "%(b)s", "{ $whole }"], + ["browsing-nd-names", "%(n)d", "{ $num }"], + ["browsing-nd-names", "%(name)s", "{ $name }"], +] + + +def transform_string_in_file(path): + obj = parse(open(path).read(), with_spans=False) + changed = False + for ent in obj.body: + if isinstance(ent, Junk): + raise Exception(f"file had junk! {path} {ent}") + if getattr(ent, "id", None): + key = ent.id.name + for (target_key, src, dst) in target_repls: + if key == target_key: + for elem in ent.value.elements: + newval = elem.value.replace(src, dst) + if newval != elem.value: + elem.value = newval + changed = True + + if changed: + open(path, "w", encoding="utf8").write(serialize(obj)) + print("updated", path) + + +for path in template_files + translation_files: + transform_string_in_file(path)