begin plural string migration
This commit is contained in:
parent
2453e5c488
commit
e527d31dfc
@ -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 = ",<br>".join(f"<b>{os.path.basename(p)}</b>" for p in paths)
|
||||
q = _(
|
||||
"<b>Important</b>: As add-ons are programs downloaded from the internet, "
|
||||
"they are potentially malicious."
|
||||
"<b>You should only install add-ons you trust.</b><br><br>"
|
||||
"Are you sure you want to proceed with the installation of the "
|
||||
"following Anki add-on(s)?<br><br>%(names)s"
|
||||
) % dict(names=names)
|
||||
q = tr(TR.ADDONS_IMPORTANT_AS_ADDONS_ARE_PROGRAMS_DOWNLOADED) % dict(
|
||||
names=names
|
||||
)
|
||||
if (
|
||||
not showInfo(
|
||||
q,
|
||||
|
@ -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?
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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", "<br>")
|
||||
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)
|
||||
|
@ -102,3 +102,19 @@ browsing-treat-input-as-regular-expression = Treat input as regular expression
|
||||
browsing-type-here-to-search = <type here to search; hit enter to show current deck>
|
||||
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
|
||||
}
|
||||
|
@ -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?
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user