begin plural string migration
This commit is contained in:
parent
2453e5c488
commit
e527d31dfc
@ -24,7 +24,7 @@ import anki
|
|||||||
import aqt
|
import aqt
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
from anki.httpclient import HttpClient
|
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 import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
@ -275,10 +275,7 @@ class AddonManager:
|
|||||||
if conflicting:
|
if conflicting:
|
||||||
addons = ", ".join(self.addonName(f) for f in conflicting)
|
addons = ", ".join(self.addonName(f) for f in conflicting)
|
||||||
showInfo(
|
showInfo(
|
||||||
_(
|
tr(TR.ADDONS_THE_FOLLOWING_ADDONS_ARE_INCOMPATIBLE_WITH)
|
||||||
"The following add-ons are incompatible with %(name)s \
|
|
||||||
and have been disabled: %(found)s"
|
|
||||||
)
|
|
||||||
% dict(name=addon.human_name(), found=addons),
|
% dict(name=addon.human_name(), found=addons),
|
||||||
textFormat="plain",
|
textFormat="plain",
|
||||||
)
|
)
|
||||||
@ -468,7 +465,9 @@ and have been disabled: %(found)s"
|
|||||||
"manifest": tr(TR.ADDONS_INVALID_ADDON_MANIFEST),
|
"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
|
if mode == "download": # preserve old format strings for i18n
|
||||||
template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS)
|
template = tr(TR.ADDONS_ERROR_DOWNLOADING_IDS_ERRORS)
|
||||||
@ -1400,13 +1399,9 @@ def installAddonPackages(
|
|||||||
|
|
||||||
if warn:
|
if warn:
|
||||||
names = ",<br>".join(f"<b>{os.path.basename(p)}</b>" for p in paths)
|
names = ",<br>".join(f"<b>{os.path.basename(p)}</b>" for p in paths)
|
||||||
q = _(
|
q = tr(TR.ADDONS_IMPORTANT_AS_ADDONS_ARE_PROGRAMS_DOWNLOADED) % dict(
|
||||||
"<b>Important</b>: As add-ons are programs downloaded from the internet, "
|
names=names
|
||||||
"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)
|
|
||||||
if (
|
if (
|
||||||
not showInfo(
|
not showInfo(
|
||||||
q,
|
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-unknown-error = Unknown error: { $val }
|
||||||
addons-view-addon-page = View Add-on Page
|
addons-view-addon-page = View Add-on Page
|
||||||
addons-view-files = View Files
|
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-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-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-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"],
|
"studying": ["Space"],
|
||||||
"qt-misc": ["&Edit", "&Guide...", "&Help", "&Undo", "Unexpected response code: %s"],
|
"qt-misc": ["&Edit", "&Guide...", "&Help", "&Undo", "Unexpected response code: %s"],
|
||||||
"adding": ["Added"],
|
"adding": ["Added"],
|
||||||
|
"browsing": ["%d note"],
|
||||||
}
|
}
|
||||||
|
|
||||||
blacklist = {"Anki", "%", "Dialog", "Center", "Left", "Right", "~", "&Cram..."}
|
blacklist = {"Anki", "%", "Dialog", "Center", "Left", "Right", "~", "&Cram..."}
|
||||||
|
|
||||||
|
|
||||||
def determine_module(text, files):
|
def determine_module(text, files):
|
||||||
|
if not files:
|
||||||
|
return None
|
||||||
if text in blacklist:
|
if text in blacklist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if text.count("%") > 1:
|
||||||
|
print("skip", text)
|
||||||
|
return None
|
||||||
|
|
||||||
if "&" in text:
|
if "&" in text:
|
||||||
return "qt-accel"
|
return "qt-accel"
|
||||||
|
|
||||||
@ -189,6 +196,7 @@ def determine_module(text, files):
|
|||||||
if len(files) == 1:
|
if len(files) == 1:
|
||||||
return list(files)[0]
|
return list(files)[0]
|
||||||
|
|
||||||
|
print(text, files)
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
@ -225,8 +233,7 @@ seen_keys = set()
|
|||||||
|
|
||||||
|
|
||||||
def migrate_entry(entry):
|
def migrate_entry(entry):
|
||||||
if entry.msgid_plural:
|
if not entry.msgid_plural:
|
||||||
# print("skip plural", entry.msgid)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
text = entry.msgid
|
text = entry.msgid
|
||||||
@ -244,7 +251,7 @@ def migrate_entry(entry):
|
|||||||
|
|
||||||
files2 = set()
|
files2 = set()
|
||||||
for file in files:
|
for file in files:
|
||||||
if file == "stats":
|
if file in ("stats", "supermemo_xml"):
|
||||||
continue
|
continue
|
||||||
file = module_map[file]
|
file = module_map[file]
|
||||||
files2.add(file)
|
files2.add(file)
|
||||||
@ -260,7 +267,7 @@ def migrate_entry(entry):
|
|||||||
seen_keys.add(key)
|
seen_keys.add(key)
|
||||||
|
|
||||||
modules.setdefault(module, [])
|
modules.setdefault(module, [])
|
||||||
modules[module].append((key, text))
|
modules[module].append((key, [entry.msgid, entry.msgid_plural]))
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -309,8 +316,8 @@ for (module, items) in modules.items():
|
|||||||
strings_by_module[module] = items
|
strings_by_module[module] = items
|
||||||
for item in items:
|
for item in items:
|
||||||
(key, text) = item
|
(key, text) = item
|
||||||
assert text not in keys_by_text
|
assert text[0] not in keys_by_text
|
||||||
keys_by_text[text] = (module, key)
|
keys_by_text[text[0]] = (module, key)
|
||||||
|
|
||||||
with open("strings_by_module.json", "w") as file:
|
with open("strings_by_module.json", "w") as file:
|
||||||
file.write(json.dumps(strings_by_module))
|
file.write(json.dumps(strings_by_module))
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from fluent.syntax import parse, serialize
|
from fluent.syntax import parse, serialize
|
||||||
from fluent.syntax.ast import Message, TextElement, Identifier, Pattern, Junk
|
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"))
|
modules = json.load(open("strings_by_module.json"))
|
||||||
translations = json.load(open("strings.json"))
|
translations = json.load(open("strings.json"))
|
||||||
|
plurals = json.load(open("plurals.json"))
|
||||||
|
|
||||||
|
|
||||||
# # fixme:
|
# # fixme:
|
||||||
# addons addons-downloaded-fnames Downloaded %(fname)s
|
# addons addons-downloaded-fnames Downloaded %(fname)s
|
||||||
@ -33,17 +38,43 @@ translations = json.load(open("strings.json"))
|
|||||||
# fixme: isolation chars
|
# fixme: isolation chars
|
||||||
|
|
||||||
|
|
||||||
def transform_text(text: str) -> str:
|
def plural_text(key, lang, translation):
|
||||||
# fixme: automatically remap to %s in a compat wrapper? manually fix?
|
lang = re.sub("(_|-).*", "", lang)
|
||||||
text = (
|
|
||||||
text.replace("%d", "{ $val }")
|
var = re.findall(r"{ (\$.*?) }", translation[0])
|
||||||
.replace("%s", "{ $val }")
|
try:
|
||||||
.replace("{}", "{ $val }")
|
var = var[0]
|
||||||
)
|
except:
|
||||||
if "Clock drift" in text:
|
print(key, lang, translation)
|
||||||
text = text.replace("\n", "<br>")
|
raise
|
||||||
else:
|
|
||||||
text = text.replace("\n", " ")
|
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
|
return text
|
||||||
|
|
||||||
|
|
||||||
@ -57,6 +88,14 @@ def check_parses(path: str):
|
|||||||
raise Exception(f"file had junk! {path} {ent}")
|
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():
|
for module, items in modules.items():
|
||||||
if module in qt_modules:
|
if module in qt_modules:
|
||||||
folder = qt
|
folder = qt
|
||||||
@ -68,8 +107,9 @@ for module, items in modules.items():
|
|||||||
print(path)
|
print(path)
|
||||||
with open(path, "a", encoding="utf8") as file:
|
with open(path, "a", encoding="utf8") as file:
|
||||||
for (key, text) in items:
|
for (key, text) in items:
|
||||||
text2 = transform_text(text)
|
key = munge_key(key)
|
||||||
file.write(f"{key} = {text2}\n")
|
text2 = transform_text(key, text, "en")
|
||||||
|
file.write(text2)
|
||||||
|
|
||||||
check_parses(path)
|
check_parses(path)
|
||||||
|
|
||||||
@ -80,8 +120,12 @@ for module, items in modules.items():
|
|||||||
|
|
||||||
out = []
|
out = []
|
||||||
for (key, text) in items:
|
for (key, text) in items:
|
||||||
if text in map:
|
if text[0] in map:
|
||||||
out.append((key, transform_text(map[text])))
|
forms = map[text[0]]
|
||||||
|
if isinstance(forms, str):
|
||||||
|
forms = [forms]
|
||||||
|
key = munge_key(key)
|
||||||
|
out.append(transform_text(key, forms, lang))
|
||||||
|
|
||||||
if out:
|
if out:
|
||||||
path = os.path.join(folder, lang, module + ".ftl")
|
path = os.path.join(folder, lang, module + ".ftl")
|
||||||
@ -91,7 +135,7 @@ for module, items in modules.items():
|
|||||||
|
|
||||||
print(path)
|
print(path)
|
||||||
with open(path, "a", encoding="utf8") as file:
|
with open(path, "a", encoding="utf8") as file:
|
||||||
for (key, text) in out:
|
for o in out:
|
||||||
file.write(f"{key} = {text}\n")
|
file.write(o)
|
||||||
|
|
||||||
check_parses(path)
|
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-type-here-to-search = <type here to search; hit enter to show current deck>
|
||||||
browsing-whole-collection = Whole Collection
|
browsing-whole-collection = Whole Collection
|
||||||
browsing-you-must-have-at-least-one = You must have at least one column.
|
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-remove-card-type = Remove Card Type...
|
||||||
card-templates-rename-card-type = Rename Card Type...
|
card-templates-rename-card-type = Rename Card Type...
|
||||||
card-templates-reposition-card-type = Reposition 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 = Study
|
||||||
decks-study-deck = Study Deck
|
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-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-include-tags = Include tags
|
||||||
exporting-notes-in-plain-text = Notes in Plain Text
|
exporting-notes-in-plain-text = Notes in Plain Text
|
||||||
exporting-selected-notes = Selected Notes
|
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-unknown-file-format = Unknown file format.
|
||||||
importing-update-existing-notes-when-first-field = Update existing notes when first field matches
|
importing-update-existing-notes-when-first-field = Update existing notes when first field matches
|
||||||
importing-updated = Updated
|
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-tag-only = Tag Only
|
||||||
scheduling-the-default-configuration-cant-be-removed = The default configuration can't be removed.
|
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-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-unbury = Unbury
|
||||||
studying-what-would-you-like-to-unbury = What would you like to 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-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