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.
+ }