diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index 4bd8c1ad5..c4ea1392e 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -24,7 +24,7 @@ import anki import aqt import aqt.forms from anki.httpclient import HttpClient -from anki.lang import _, ngettext, without_unicode_isolation +from anki.lang import ngettext, without_unicode_isolation from aqt import gui_hooks from aqt.qt import * from aqt.utils import ( @@ -275,10 +275,7 @@ class AddonManager: if conflicting: addons = ", ".join(self.addonName(f) for f in conflicting) showInfo( - _( - "The following add-ons are incompatible with %(name)s \ -and have been disabled: %(found)s" - ) + tr(TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH) % dict(name=addon.human_name(), found=addons), textFormat="plain", ) @@ -468,7 +465,9 @@ and have been disabled: %(found)s" "manifest": tr(TR.ADDONS_INVALID_ADDON_MANIFEST), } - msg = messages.get(result.errmsg, _("Unknown error: {}".format(result.errmsg))) + msg = messages.get( + 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) @@ -1400,13 +1399,9 @@ def installAddonPackages( if warn: names = ",
".join(f"{os.path.basename(p)}" for p in paths) - q = _( - "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" - ) % dict(names=names) + q = tr(TR.ADDONS_IMPORTANT_AS_ADDONS_ARE_PROGRAMS_DOWNLOADED) % dict( + names=names + ) if ( not showInfo( q, diff --git a/qt/ftl/addons.ftl b/qt/ftl/addons.ftl index 2a75836a9..89c8588f6 100644 --- a/qt/ftl/addons.ftl +++ b/qt/ftl/addons.ftl @@ -59,3 +59,7 @@ 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? + } diff --git a/qt/ftl/qt-misc.ftl b/qt/ftl/qt-misc.ftl index 84b7c5e25..bac7bc46c 100644 --- a/qt/ftl/qt-misc.ftl +++ b/qt/ftl/qt-misc.ftl @@ -61,3 +61,7 @@ qt-misc-would-you-like-to-download-it = Would you like to download it now? qt-misc-your-collection-file-appears-to-be = Your collection file appears to be corrupt. This can happen when the file is copied or moved while Anki is open, or when the collection is stored on a network or cloud drive. If problems persist after restarting your computer, please open an automatic backup from the profile screen. qt-misc-your-computers-storage-may-be-full = Your computer's storage may be full. Please delete some unneeded files, then try again. qt-misc-your-firewall-or-antivirus-program-is = Your firewall or antivirus program is preventing Anki from creating a connection to itself. Please add an exception for Anki. +qt-misc-second = { $count -> + [one] { $count } second + *[other] { $count } seconds + } diff --git a/qt/po/scripts/extract-po-strings.py b/qt/po/scripts/extract-po-strings.py index 7e1abfb65..56df14f70 100644 --- a/qt/po/scripts/extract-po-strings.py +++ b/qt/po/scripts/extract-po-strings.py @@ -170,15 +170,22 @@ text_remap = { "studying": ["Space"], "qt-misc": ["&Edit", "&Guide...", "&Help", "&Undo", "Unexpected response code: %s"], "adding": ["Added"], + "browsing": ["%d note"], } blacklist = {"Anki", "%", "Dialog", "Center", "Left", "Right", "~", "&Cram..."} def determine_module(text, files): + if not files: + return None if text in blacklist: return None + if text.count("%") > 1: + print("skip", text) + return None + if "&" in text: return "qt-accel" @@ -189,6 +196,7 @@ def determine_module(text, files): if len(files) == 1: return list(files)[0] + print(text, files) assert False @@ -225,8 +233,7 @@ seen_keys = set() def migrate_entry(entry): - if entry.msgid_plural: - # print("skip plural", entry.msgid) + if not entry.msgid_plural: return text = entry.msgid @@ -244,7 +251,7 @@ def migrate_entry(entry): files2 = set() for file in files: - if file == "stats": + if file in ("stats", "supermemo_xml"): continue file = module_map[file] files2.add(file) @@ -260,7 +267,7 @@ def migrate_entry(entry): seen_keys.add(key) modules.setdefault(module, []) - modules[module].append((key, text)) + modules[module].append((key, [entry.msgid, entry.msgid_plural])) return None @@ -309,8 +316,8 @@ for (module, items) in modules.items(): strings_by_module[module] = items for item in items: (key, text) = item - assert text not in keys_by_text - keys_by_text[text] = (module, key) + assert text[0] not in keys_by_text + keys_by_text[text[0]] = (module, key) with open("strings_by_module.json", "w") as file: file.write(json.dumps(strings_by_module)) diff --git a/qt/po/scripts/write-ftls.py b/qt/po/scripts/write-ftls.py index aed4c6c3f..231ced54b 100644 --- a/qt/po/scripts/write-ftls.py +++ b/qt/po/scripts/write-ftls.py @@ -2,6 +2,9 @@ import json import os +import re + +from typing import List from fluent.syntax import parse, serialize from fluent.syntax.ast import Message, TextElement, Identifier, Pattern, Junk @@ -13,6 +16,8 @@ qt_modules = {"about", "qt-accel", "addons", "qt-misc"} modules = json.load(open("strings_by_module.json")) translations = json.load(open("strings.json")) +plurals = json.load(open("plurals.json")) + # # fixme: # addons addons-downloaded-fnames Downloaded %(fname)s @@ -33,17 +38,43 @@ translations = json.load(open("strings.json")) # fixme: isolation chars -def transform_text(text: str) -> str: - # fixme: automatically remap to %s in a compat wrapper? manually fix? - text = ( - text.replace("%d", "{ $val }") - .replace("%s", "{ $val }") - .replace("{}", "{ $val }") - ) - if "Clock drift" in text: - text = text.replace("\n", "
") - else: - text = text.replace("\n", " ") +def plural_text(key, lang, translation): + lang = re.sub("(_|-).*", "", lang) + + var = re.findall(r"{ (\$.*?) }", translation[0]) + try: + var = var[0] + except: + print(key, lang, translation) + raise + + buf = f"{key} = {{ {var} ->\n" + + # for each of the plural forms except the last + for idx, msg in enumerate(translation[:-1]): + plural_form = plurals[lang][idx] + buf += f" [{plural_form}] {msg}\n" + + # add the catchall + msg = translation[-1] + buf += f" *[other] {msg}\n" + buf += " }\n" + return buf + + +def transform_text(key: str, texts: List[str], lang: str) -> str: + texts = [ + ( + text.replace("%d", "{ $count }") + .replace("%s", "{ $count }") + .replace("%(num)d", "{ $count }") + ) + for text in texts + ] + + text = plural_text(key, lang, texts) + print(text) + return text @@ -57,6 +88,14 @@ def check_parses(path: str): raise Exception(f"file had junk! {path} {ent}") +def munge_key(key): + if key == "browsing-note": + return "browsing-note-count" + if key == "card-templates-card": + return "card-templates-card-count" + return key + + for module, items in modules.items(): if module in qt_modules: folder = qt @@ -68,8 +107,9 @@ for module, items in modules.items(): print(path) with open(path, "a", encoding="utf8") as file: for (key, text) in items: - text2 = transform_text(text) - file.write(f"{key} = {text2}\n") + key = munge_key(key) + text2 = transform_text(key, text, "en") + file.write(text2) check_parses(path) @@ -80,8 +120,12 @@ for module, items in modules.items(): out = [] for (key, text) in items: - if text in map: - out.append((key, transform_text(map[text]))) + if text[0] in map: + forms = map[text[0]] + if isinstance(forms, str): + forms = [forms] + key = munge_key(key) + out.append(transform_text(key, forms, lang)) if out: path = os.path.join(folder, lang, module + ".ftl") @@ -91,7 +135,7 @@ for module, items in modules.items(): print(path) with open(path, "a", encoding="utf8") as file: - for (key, text) in out: - file.write(f"{key} = {text}\n") + for o in out: + file.write(o) check_parses(path) diff --git a/rslib/ftl/browsing.ftl b/rslib/ftl/browsing.ftl index d5e120ecc..1046ce348 100644 --- a/rslib/ftl/browsing.ftl +++ b/rslib/ftl/browsing.ftl @@ -102,3 +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-selected = { $count -> + [one] { $count } selected + *[other] { $count } selected + } diff --git a/rslib/ftl/card-templates.ftl b/rslib/ftl/card-templates.ftl index 2beb4e868..8b66fc00e 100644 --- a/rslib/ftl/card-templates.ftl +++ b/rslib/ftl/card-templates.ftl @@ -40,3 +40,11 @@ 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? + } diff --git a/rslib/ftl/decks.ftl b/rslib/ftl/decks.ftl index c6e263fd3..4eecee62a 100644 --- a/rslib/ftl/decks.ftl +++ b/rslib/ftl/decks.ftl @@ -31,3 +31,7 @@ decks-reschedule-cards-based-on-my-answers = Reschedule cards based on my answer decks-study = Study decks-study-deck = Study Deck decks-the-provided-search-did-not-match = The provided search did not match any cards. Would you like to revise it? +decks-it-has-card = { $count -> + [one] It has { $count } card. + *[other] It has { $count } cards. + } diff --git a/rslib/ftl/exporting.ftl b/rslib/ftl/exporting.ftl index f22b7f0e6..7d66820c5 100644 --- a/rslib/ftl/exporting.ftl +++ b/rslib/ftl/exporting.ftl @@ -15,3 +15,15 @@ exporting-include-scheduling-information = Include scheduling information exporting-include-tags = Include tags exporting-notes-in-plain-text = Notes in Plain Text exporting-selected-notes = Selected Notes +exporting-card-exported = { $count -> + [one] { $count } card exported. + *[other] { $count } cards exported. + } +exporting-exported-media-file = { $count -> + [one] Exported { $count } media file + *[other] Exported { $count } media files + } +exporting-note-exported = { $count -> + [one] { $count } note exported. + *[other] { $count } notes exported. + } diff --git a/rslib/ftl/importing.ftl b/rslib/ftl/importing.ftl index 76a080c61..08c7413da 100644 --- a/rslib/ftl/importing.ftl +++ b/rslib/ftl/importing.ftl @@ -52,3 +52,23 @@ 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 + } diff --git a/rslib/ftl/scheduling.ftl b/rslib/ftl/scheduling.ftl index b41311af9..be774a8d0 100644 --- a/rslib/ftl/scheduling.ftl +++ b/rslib/ftl/scheduling.ftl @@ -138,3 +138,7 @@ scheduling-steps-must-be-numbers = Steps must be numbers. scheduling-tag-only = Tag Only scheduling-the-default-configuration-cant-be-removed = The default configuration can't be removed. scheduling-your-changes-will-affect-multiple-decks = Your changes will affect multiple decks. If you wish to change only the current deck, please add a new options group first. +scheduling-deck-updated = { $count -> + [one] { $count } deck updated. + *[other] { $count } decks updated. + } diff --git a/rslib/ftl/studying.ftl b/rslib/ftl/studying.ftl index 5963d268a..8f8b6180e 100644 --- a/rslib/ftl/studying.ftl +++ b/rslib/ftl/studying.ftl @@ -42,3 +42,15 @@ studying-type-answer-unknown-field = Type answer: unknown field { $val } studying-unbury = Unbury studying-what-would-you-like-to-unbury = What would you like to unbury? studying-you-havent-recorded-your-voice-yet = You haven't recorded your voice yet. +studying-card-studied-in = { $count -> + [one] { $count } card studied in + *[other] { $count } cards studied in + } +studying-minute = { $count -> + [one] { $count } minute. + *[other] { $count } minutes. + } +studying-note-and-its-card-deleted = { $count -> + [one] Note and its { $count } card deleted. + *[other] Note and its { $count } cards deleted. + }