mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
strip out unused gettext refs
This commit is contained in:
parent
ab69ca31ec
commit
1c5f94d46f
23 changed files with 18 additions and 2020 deletions
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
|
@ -139,7 +139,7 @@ jobs:
|
|||
|
||||
set -x
|
||||
sudo apt update
|
||||
sudo apt install portaudio19-dev gettext
|
||||
sudo apt install portaudio19-dev
|
||||
curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.7.4/bazelisk-linux-amd64 -o ./bazel && \
|
||||
chmod +x ./bazel
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ These instructions are written for Debian/Ubuntu; adjust for your distribution.
|
|||
**Ensure some basic tools are installed**:
|
||||
|
||||
```
|
||||
$ sudo apt install bash grep findutils curl gcc g++ git gettext
|
||||
$ sudo apt install bash grep findutils curl gcc g++ git
|
||||
```
|
||||
|
||||
The 'find' utility is 'findutils' on Debian.
|
||||
|
|
|
@ -14,7 +14,7 @@ Install Homebrew from <https://brew.sh/>
|
|||
Then install deps:
|
||||
|
||||
```
|
||||
$ brew install rsync gettext bazelisk
|
||||
$ brew install rsync bazelisk
|
||||
```
|
||||
|
||||
**Install Python 3.8**:
|
||||
|
|
|
@ -31,7 +31,7 @@ Install [msys2](https://www.msys2.org/) into the default folder location.
|
|||
After installation completes, run msys2, and run the following command:
|
||||
|
||||
```
|
||||
$ pacman -S git gettext
|
||||
$ pacman -S git
|
||||
```
|
||||
|
||||
**Bazelisk**:
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
# Please leave the coding line in this file to prevent xgettext complaining.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import gettext
|
||||
import re
|
||||
from typing import Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
import anki
|
||||
|
||||
|
@ -142,11 +139,6 @@ def lang_to_disk_lang(lang: str) -> str:
|
|||
# the currently set interface language
|
||||
currentLang = "en"
|
||||
|
||||
# the current gettext translation catalog
|
||||
current_catalog: Optional[
|
||||
Union[gettext.NullTranslations, gettext.GNUTranslations]
|
||||
] = None
|
||||
|
||||
# the current Fluent translation instance
|
||||
current_i18n: Optional[anki.rsbackend.RustBackend] = None
|
||||
|
||||
|
@ -155,10 +147,13 @@ locale_folder = ""
|
|||
|
||||
|
||||
def _(str: str) -> str:
|
||||
if current_catalog:
|
||||
return current_catalog.gettext(str)
|
||||
else:
|
||||
return str
|
||||
print(f"gettext _() is deprecated: {str}")
|
||||
return str
|
||||
|
||||
|
||||
def ngettext(single: str, plural: str, n: int) -> str:
|
||||
print(f"ngettext() is deprecated: {plural}")
|
||||
return plural
|
||||
|
||||
|
||||
def tr_legacyglobal(*args, **kwargs) -> str:
|
||||
|
@ -169,26 +164,10 @@ def tr_legacyglobal(*args, **kwargs) -> str:
|
|||
return "tr_legacyglobal() called without active backend"
|
||||
|
||||
|
||||
def ngettext(single: str, plural: str, n: int) -> str:
|
||||
if current_catalog:
|
||||
return current_catalog.ngettext(single, plural, n)
|
||||
elif n == 1:
|
||||
return single
|
||||
return plural
|
||||
|
||||
|
||||
def set_lang(lang: str, locale_dir: str) -> None:
|
||||
global currentLang, current_catalog, current_i18n, locale_folder
|
||||
gettext_dir = locale_dir
|
||||
ftl_dir = locale_dir
|
||||
|
||||
global currentLang, current_i18n, locale_folder
|
||||
currentLang = lang
|
||||
current_catalog = gettext.translation(
|
||||
"anki", gettext_dir, languages=[lang], fallback=True
|
||||
)
|
||||
|
||||
current_i18n = anki.rsbackend.RustBackend(ftl_folder=ftl_dir, langs=[lang])
|
||||
|
||||
current_i18n = anki.rsbackend.RustBackend(ftl_folder=locale_folder, langs=[lang])
|
||||
locale_folder = locale_dir
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import argparse
|
||||
import builtins
|
||||
import getpass
|
||||
import gettext
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
|
@ -167,7 +166,7 @@ dialogs = DialogManager()
|
|||
# Language handling
|
||||
##########################################################################
|
||||
# Qt requires its translator to be installed before any GUI widgets are
|
||||
# loaded, and we need the Qt language to match the gettext language or
|
||||
# loaded, and we need the Qt language to match the i18n language or
|
||||
# translated shortcuts will not work.
|
||||
|
||||
# A reference to the Qt translator needs to be held to prevent it from
|
||||
|
@ -202,7 +201,7 @@ def setupLangAndBackend(
|
|||
lang = force or pm.meta["defaultLang"]
|
||||
lang = anki.lang.lang_to_disk_lang(lang)
|
||||
|
||||
# load gettext catalog
|
||||
# set active language
|
||||
ldir = locale_dir()
|
||||
anki.lang.set_lang(lang, ldir)
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
filegroup(
|
||||
name = "data",
|
||||
srcs = [
|
||||
"//qt/aqt/data/locale",
|
||||
"//qt/aqt/data/web",
|
||||
],
|
||||
visibility = ["//qt:__subpackages__"],
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
load("//qt/po:gettext.bzl", "compile_all_po_files")
|
||||
|
||||
compile_all_po_files(
|
||||
name = "locale",
|
||||
visibility = ["//qt:__subpackages__"],
|
||||
)
|
|
@ -9,6 +9,7 @@
|
|||
# included implicitly in the past, and relied upon by some add-ons
|
||||
import cgi
|
||||
import decimal
|
||||
import gettext
|
||||
|
||||
# useful for add-ons
|
||||
import logging
|
||||
|
|
1
qt/po/.gitignore
vendored
1
qt/po/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
strings*.json
|
|
@ -1,17 +0,0 @@
|
|||
load(":gettext.bzl", "build_template", "update_all_po_files")
|
||||
|
||||
build_template(
|
||||
name = "pot",
|
||||
srcs = [
|
||||
"//pylib/anki:py_source_files",
|
||||
"//qt/aqt:py_source_files",
|
||||
"//qt/aqt/forms",
|
||||
],
|
||||
pot_file = "anki.pot",
|
||||
)
|
||||
|
||||
update_all_po_files(
|
||||
name = "po_files",
|
||||
pot_file = "anki.pot",
|
||||
visibility = ["//qt/aqt:__subpackages__"],
|
||||
)
|
|
@ -1,148 +0,0 @@
|
|||
_langs = [
|
||||
"af",
|
||||
"ar",
|
||||
"bg",
|
||||
"ca",
|
||||
"cs",
|
||||
"da",
|
||||
"de",
|
||||
"el",
|
||||
"en-GB",
|
||||
"eo",
|
||||
"es",
|
||||
"et",
|
||||
"eu",
|
||||
"fa",
|
||||
"fi",
|
||||
"fr",
|
||||
"ga-IE",
|
||||
"gl",
|
||||
"he",
|
||||
"hi-IN",
|
||||
"hr",
|
||||
"hu",
|
||||
"hy-AM",
|
||||
"it",
|
||||
"ja",
|
||||
"jbo",
|
||||
"kab",
|
||||
# "km",
|
||||
"ko",
|
||||
"la",
|
||||
"mn",
|
||||
"mr",
|
||||
"ms",
|
||||
"nb-NO",
|
||||
"nl",
|
||||
"nn-NO",
|
||||
"oc",
|
||||
"or",
|
||||
"pl",
|
||||
"pt-BR",
|
||||
"pt-PT",
|
||||
"ro",
|
||||
"ru",
|
||||
"sk",
|
||||
"sl",
|
||||
"sr",
|
||||
"sv-SE",
|
||||
"th",
|
||||
"tr",
|
||||
"uk",
|
||||
# "ur",
|
||||
"vi",
|
||||
"zh-CN",
|
||||
"zh-TW",
|
||||
]
|
||||
|
||||
# homebrew gettext is not on path by default
|
||||
_pathfix = """export PATH="$$PATH":/usr/local/opt/gettext/bin\n"""
|
||||
|
||||
def update_po(name, po_file_in, po_file_out, pot_file, visibility):
|
||||
"Merge old .po and latest strings from .pot into new .po"
|
||||
native.genrule(
|
||||
name = name,
|
||||
srcs = [po_file_in, pot_file],
|
||||
outs = [po_file_out],
|
||||
cmd = _pathfix + """\
|
||||
msgmerge -q -F --no-wrap $(location {po_file_in}) $(location {pot_file}) > $(location {po_file_out})
|
||||
""".format(
|
||||
po_file_in = po_file_in,
|
||||
po_file_out = po_file_out,
|
||||
pot_file = pot_file,
|
||||
),
|
||||
message = "Updating translation",
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
def compile_po(name, po_file, mo_file):
|
||||
"Build .mo file from an updated .po file."
|
||||
native.genrule(
|
||||
name = name,
|
||||
srcs = [po_file],
|
||||
outs = [mo_file],
|
||||
cmd = _pathfix + """\
|
||||
cat $(location {po_file}) | msgfmt - --output-file=$(location {mo_file})
|
||||
""".format(
|
||||
po_file = po_file,
|
||||
mo_file = mo_file,
|
||||
),
|
||||
message = "Compiling translation",
|
||||
)
|
||||
|
||||
def build_template(name, pot_file, srcs):
|
||||
"Build .pot file from Python files."
|
||||
native.genrule(
|
||||
name = name,
|
||||
srcs = srcs,
|
||||
outs = [pot_file],
|
||||
cmd = _pathfix + """\
|
||||
all=all.files
|
||||
for i in $(SRCS); do
|
||||
echo $$i >> $$all
|
||||
done
|
||||
xgettext -cT: -s --no-wrap --files-from=$$all --output=$(OUTS)
|
||||
rm $$all
|
||||
""",
|
||||
message = "Building .pot template",
|
||||
)
|
||||
|
||||
def update_all_po_files(name, pot_file, visibility):
|
||||
# merge external .po files with updated .pot
|
||||
po_files = []
|
||||
for lang in _langs:
|
||||
po_file_in = "@aqt_po//:desktop/{}/anki.po".format(lang)
|
||||
po_file_out = "{}/anki.po".format(lang)
|
||||
update_po(
|
||||
name = lang + "_po",
|
||||
po_file_in = po_file_in,
|
||||
po_file_out = po_file_out,
|
||||
pot_file = pot_file,
|
||||
visibility = visibility,
|
||||
)
|
||||
po_files.append(po_file_out)
|
||||
|
||||
native.filegroup(
|
||||
name = name,
|
||||
srcs = po_files,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
def compile_all_po_files(name, visibility):
|
||||
"Build all .mo files from .po files."
|
||||
mo_files = []
|
||||
for lang in _langs:
|
||||
po_file = "//qt/po:{}/anki.po".format(lang)
|
||||
mo_file = "{}/LC_MESSAGES/anki.mo".format(lang)
|
||||
compile_po(
|
||||
name = lang + "_mo",
|
||||
po_file = po_file,
|
||||
mo_file = mo_file,
|
||||
)
|
||||
mo_files.append(mo_file)
|
||||
|
||||
native.filegroup(
|
||||
name = name,
|
||||
srcs = mo_files,
|
||||
visibility = visibility,
|
||||
)
|
|
@ -1,889 +0,0 @@
|
|||
{
|
||||
"af": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ak": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"am": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"an": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ar": [
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"ars": [
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"as": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"asa": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ast": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"az": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"be": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"bem": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"bez": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"bg": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"bho": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"bm": [
|
||||
"other"
|
||||
],
|
||||
"bn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"bo": [
|
||||
"other"
|
||||
],
|
||||
"br": [
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"brx": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"bs": [
|
||||
"one",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"ca": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ce": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ceb": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"cgg": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"chr": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ckb": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"cs": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"cy": [
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"da": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"de": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"dsb": [
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"dv": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"dz": [
|
||||
"other"
|
||||
],
|
||||
"ee": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"el": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"en": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"eo": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"es": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"et": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"eu": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"fa": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ff": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"fi": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"fil": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"fo": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"fr": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"fur": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"fy": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ga": [
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"gd": [
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"gl": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"gsw": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"gu": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"guw": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"gv": [
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"ha": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"haw": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"he": [
|
||||
"one",
|
||||
"two",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"hi": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"hr": [
|
||||
"one",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"hsb": [
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"hu": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"hy": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ia": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"id": [
|
||||
"other"
|
||||
],
|
||||
"ig": [
|
||||
"other"
|
||||
],
|
||||
"ii": [
|
||||
"other"
|
||||
],
|
||||
"in": [
|
||||
"other"
|
||||
],
|
||||
"io": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"is": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"it": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"iu": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"iw": [
|
||||
"one",
|
||||
"two",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"ja": [
|
||||
"other"
|
||||
],
|
||||
"jbo": [
|
||||
"other"
|
||||
],
|
||||
"jgo": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ji": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"jmc": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"jv": [
|
||||
"other"
|
||||
],
|
||||
"jw": [
|
||||
"other"
|
||||
],
|
||||
"ka": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"kab": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"kaj": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"kcg": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"kde": [
|
||||
"other"
|
||||
],
|
||||
"kea": [
|
||||
"other"
|
||||
],
|
||||
"kk": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"kkj": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"kl": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"km": [
|
||||
"other"
|
||||
],
|
||||
"kn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ko": [
|
||||
"other"
|
||||
],
|
||||
"ks": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ksb": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ksh": [
|
||||
"zero",
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ku": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"kw": [
|
||||
"zero",
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"ky": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"lag": [
|
||||
"zero",
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"lb": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"lg": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"lkt": [
|
||||
"other"
|
||||
],
|
||||
"ln": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"lo": [
|
||||
"other"
|
||||
],
|
||||
"lt": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"lv": [
|
||||
"zero",
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"mas": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"mg": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"mgo": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"mk": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ml": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"mn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"mo": [
|
||||
"one",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"mr": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ms": [
|
||||
"other"
|
||||
],
|
||||
"mt": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"my": [
|
||||
"other"
|
||||
],
|
||||
"nah": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"naq": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"nb": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"nd": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ne": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"nl": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"nn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"nnh": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"no": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"nqo": [
|
||||
"other"
|
||||
],
|
||||
"nr": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"nso": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ny": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"nyn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"om": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"or": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"os": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"osa": [
|
||||
"other"
|
||||
],
|
||||
"pa": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"pap": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"pl": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"prg": [
|
||||
"zero",
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ps": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"pt": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"pt-PT": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"rm": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ro": [
|
||||
"one",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"rof": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"root": [
|
||||
"other"
|
||||
],
|
||||
"ru": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"rwk": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sah": [
|
||||
"other"
|
||||
],
|
||||
"saq": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sc": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"scn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sd": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sdh": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"se": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"seh": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ses": [
|
||||
"other"
|
||||
],
|
||||
"sg": [
|
||||
"other"
|
||||
],
|
||||
"sh": [
|
||||
"one",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"shi": [
|
||||
"one",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"si": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sk": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"sl": [
|
||||
"one",
|
||||
"two",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"sma": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"smi": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"smj": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"smn": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"sms": [
|
||||
"one",
|
||||
"two",
|
||||
"other"
|
||||
],
|
||||
"sn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"so": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sq": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sr": [
|
||||
"one",
|
||||
"few",
|
||||
"other"
|
||||
],
|
||||
"ss": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ssy": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"st": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"su": [
|
||||
"other"
|
||||
],
|
||||
"sv": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"sw": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"syr": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ta": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"te": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"teo": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"th": [
|
||||
"other"
|
||||
],
|
||||
"ti": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"tig": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"tk": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"tl": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"tn": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"to": [
|
||||
"other"
|
||||
],
|
||||
"tr": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ts": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"tzm": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ug": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"uk": [
|
||||
"one",
|
||||
"few",
|
||||
"many",
|
||||
"other"
|
||||
],
|
||||
"ur": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"uz": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"ve": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"vi": [
|
||||
"other"
|
||||
],
|
||||
"vo": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"vun": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"wa": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"wae": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"wo": [
|
||||
"other"
|
||||
],
|
||||
"xh": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"xog": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"yi": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"yo": [
|
||||
"other"
|
||||
],
|
||||
"yue": [
|
||||
"other"
|
||||
],
|
||||
"zh": [
|
||||
"other"
|
||||
],
|
||||
"zu": [
|
||||
"one",
|
||||
"other"
|
||||
],
|
||||
"oc":
|
||||
["one", "other"],
|
||||
"la":
|
||||
["one", "other"]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
polib
|
|
@ -1,313 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import os
|
||||
import json
|
||||
import polib
|
||||
import pprint
|
||||
import re
|
||||
|
||||
# Read strings from all .po and .pot files and store them in a JSON file
|
||||
# for quick access.
|
||||
|
||||
# returns a string, an array of plurals, or None if there's no translation
|
||||
def get_msgstr(entry):
|
||||
# non-empty single string?
|
||||
if entry.msgstr:
|
||||
return entry.msgstr
|
||||
# plural string and non-empty?
|
||||
elif entry.msgstr_plural and entry.msgstr_plural[0]:
|
||||
# convert the dict into a list in the correct order
|
||||
plurals = list(entry.msgstr_plural.items())
|
||||
plurals.sort()
|
||||
# update variables and discard keys
|
||||
adjusted = []
|
||||
for _k, msg in plurals:
|
||||
assert msg
|
||||
adjusted.append(msg)
|
||||
if len(adjusted) > 1 and adjusted[0]:
|
||||
return adjusted
|
||||
else:
|
||||
if adjusted[0]:
|
||||
return adjusted[0]
|
||||
return None
|
||||
|
||||
|
||||
module_map = {
|
||||
"__init__": "qt-misc",
|
||||
"about": "about",
|
||||
"addcards": "adding",
|
||||
"addfield": "fields",
|
||||
"addmodel": "notetypes",
|
||||
"addonconf": "addons",
|
||||
"addons": "addons",
|
||||
"anki2": "importing",
|
||||
"browser": "browsing",
|
||||
"browserdisp": "browsing",
|
||||
"browseropts": "browsing",
|
||||
"changemap": "browsing",
|
||||
"changemodel": "browsing",
|
||||
"clayout_top": "card-templates",
|
||||
"clayout": "card-templates",
|
||||
"collection": "collection",
|
||||
"consts": "consts",
|
||||
"csvfile": "importing",
|
||||
"customstudy": "custom-study",
|
||||
"dconf": "scheduling",
|
||||
"debug": "qt-misc",
|
||||
"deckbrowser": "decks",
|
||||
"deckchooser": "qt-misc",
|
||||
"deckconf": "scheduling",
|
||||
"decks": "decks",
|
||||
"dyndconf": "decks",
|
||||
"dyndeckconf": "decks",
|
||||
"editaddon": "addons",
|
||||
"editcurrent": "editing",
|
||||
"edithtml": "editing",
|
||||
"editor": "editing",
|
||||
"errors": "qt-misc",
|
||||
"exporting": "exporting",
|
||||
"fields": "fields",
|
||||
"finddupes": "browsing",
|
||||
"findreplace": "browsing",
|
||||
"getaddons": "addons",
|
||||
"importing": "importing",
|
||||
"latex": "media",
|
||||
"main": "qt-misc",
|
||||
"mnemo": "importing",
|
||||
"modelchooser": "qt-misc",
|
||||
"modelopts": "notetypes",
|
||||
"models": "notetypes",
|
||||
"noteimp": "importing",
|
||||
"overview": "studying",
|
||||
"preferences": "preferences",
|
||||
"previewer": "qt-misc",
|
||||
"profiles": "profiles",
|
||||
"progress": "qt-misc",
|
||||
"reposition": "browsing",
|
||||
"reschedule": "browsing",
|
||||
"reviewer": "studying",
|
||||
"schedv2": "scheduling",
|
||||
"setgroup": "browsing",
|
||||
"setlang": "preferences",
|
||||
"sidebar": "browsing",
|
||||
"sound": "media",
|
||||
"studydeck": "decks",
|
||||
"taglimit": "custom-study",
|
||||
"template": "card-templates",
|
||||
"toolbar": "qt-misc",
|
||||
"update": "qt-misc",
|
||||
"utils": "qt-misc",
|
||||
"webview": "qt-misc",
|
||||
"stats": "statistics",
|
||||
}
|
||||
|
||||
text_remap = {
|
||||
"actions": [
|
||||
"Add",
|
||||
"Cancel",
|
||||
"Choose",
|
||||
"Close",
|
||||
"Copy",
|
||||
"Decks",
|
||||
"Delete",
|
||||
"Export",
|
||||
"Filter",
|
||||
"Help",
|
||||
"Import",
|
||||
"Manage...",
|
||||
"Name:",
|
||||
"New name:",
|
||||
"New",
|
||||
"Options for %s",
|
||||
"Options",
|
||||
"Preview",
|
||||
"Rebuild",
|
||||
"Rename Deck",
|
||||
"Rename",
|
||||
"Replay Audio",
|
||||
"Reposition",
|
||||
"Save",
|
||||
"Search",
|
||||
"Shortcut key: %s",
|
||||
"Suspend Card",
|
||||
"Blue Flag",
|
||||
"Green Flag",
|
||||
"Orange Flag",
|
||||
"Red Flag",
|
||||
"Custom Study",
|
||||
],
|
||||
"decks": [
|
||||
"Deck",
|
||||
"New deck name:",
|
||||
"Decreasing intervals",
|
||||
"Increasing intervals",
|
||||
"Latest added first",
|
||||
"Most lapses",
|
||||
"Oldest seen first",
|
||||
"Order added",
|
||||
"Order due",
|
||||
"Random",
|
||||
"Relative overdueness",
|
||||
],
|
||||
"scheduling": [
|
||||
"days",
|
||||
"Lapses",
|
||||
"Reviews",
|
||||
"At least one step is required.",
|
||||
"Steps must be numbers.",
|
||||
"Show new cards in order added",
|
||||
"Show new cards in random order",
|
||||
"Show new cards after reviews",
|
||||
"Show new cards before reviews",
|
||||
"Mix new cards and reviews",
|
||||
"Learning",
|
||||
"Review",
|
||||
],
|
||||
"fields": ["Add Field"],
|
||||
"editing": ["Tags", "Cards", "Fields", "LaTeX"],
|
||||
"notetypes": ["Type", "Note Types"],
|
||||
"studying": ["Space"],
|
||||
"qt-misc": ["&Edit", "&Guide...", "&Help", "&Undo", "Unexpected response code: %s"],
|
||||
"adding": ["Added"],
|
||||
}
|
||||
|
||||
blacklist = {"Anki", "%", "Dialog", "Center", "Left", "Right", "~", "&Cram..."}
|
||||
|
||||
|
||||
def determine_module(text, files):
|
||||
if text in blacklist:
|
||||
return None
|
||||
|
||||
if "&" in text:
|
||||
return "qt-accel"
|
||||
|
||||
for (module, texts) in text_remap.items():
|
||||
if text in texts:
|
||||
return module
|
||||
|
||||
if len(files) == 1:
|
||||
return list(files)[0]
|
||||
|
||||
assert False
|
||||
|
||||
|
||||
modules = dict()
|
||||
|
||||
remap_keys = {
|
||||
"browsing-": "browsing-type-here-to-search",
|
||||
"importing-": "importing-ignored",
|
||||
"qt-misc-": "qt-misc-non-unicode-text",
|
||||
}
|
||||
|
||||
|
||||
def generate_key(module: str, text: str) -> str:
|
||||
key = re.sub("<.*?>", "", text)
|
||||
key = re.sub("%[dsf.0-9]+", "", key)
|
||||
key = key.replace("+", "and")
|
||||
key = re.sub("[^a-z0-9 ]", "", key.lower())
|
||||
words = key.split(" ")
|
||||
if len(words) > 6:
|
||||
words = words[:6]
|
||||
key = "-".join(words)
|
||||
key = re.sub("--+", "-", key)
|
||||
key = re.sub("-$|^-", "", key)
|
||||
|
||||
key = f"{module}-{key}"
|
||||
|
||||
if key in remap_keys:
|
||||
key = remap_keys[key]
|
||||
|
||||
return key
|
||||
|
||||
|
||||
seen_keys = set()
|
||||
|
||||
|
||||
def migrate_entry(entry):
|
||||
if entry.msgid_plural:
|
||||
# print("skip plural", entry.msgid)
|
||||
return
|
||||
|
||||
entry.occurrences = [e for e in entry.occurrences if "aqt/stats.py" in e[0]]
|
||||
if not entry.occurrences:
|
||||
return None
|
||||
|
||||
print(entry.occurrences)
|
||||
text = entry.msgid
|
||||
files = set(
|
||||
[os.path.splitext(os.path.basename(e[0]))[0] for e in entry.occurrences]
|
||||
)
|
||||
|
||||
files2 = set()
|
||||
for file in files:
|
||||
file = module_map[file]
|
||||
files2.add(file)
|
||||
module = determine_module(text, files2)
|
||||
if not module:
|
||||
return
|
||||
|
||||
key = generate_key(module, text)
|
||||
|
||||
if key in seen_keys:
|
||||
key += "2"
|
||||
assert key not in seen_keys
|
||||
seen_keys.add(key)
|
||||
|
||||
modules.setdefault(module, [])
|
||||
modules[module].append((key, text))
|
||||
|
||||
return None
|
||||
|
||||
|
||||
langs = {}
|
||||
|
||||
# .pot first
|
||||
base = "../../../anki-i18n/qtpo/desktop"
|
||||
pot = os.path.join(base, "anki.pot")
|
||||
pot_cat = polib.pofile(pot)
|
||||
|
||||
migration_map = []
|
||||
|
||||
for entry in pot_cat:
|
||||
if entry.msgid_plural:
|
||||
msgstr = [entry.msgid, entry.msgid_plural]
|
||||
else:
|
||||
msgstr = entry.msgid
|
||||
|
||||
langs.setdefault("en", {})[entry.msgid] = msgstr
|
||||
|
||||
if d := migrate_entry(entry):
|
||||
migration_map.append(d)
|
||||
|
||||
|
||||
# then .po files
|
||||
folders = (d for d in os.listdir(base) if d != "anki.pot")
|
||||
for lang in folders:
|
||||
po_path = os.path.join(base, lang, "anki.po")
|
||||
cat = polib.pofile(po_path)
|
||||
for entry in cat:
|
||||
msgstr = get_msgstr(entry)
|
||||
if not msgstr:
|
||||
continue
|
||||
langs.setdefault(lang, {})[entry.msgid] = msgstr
|
||||
|
||||
with open("strings.json", "w") as file:
|
||||
file.write(json.dumps(langs))
|
||||
print("wrote to strings.json")
|
||||
|
||||
# old text -> (module, new key)
|
||||
strings_by_module = {}
|
||||
keys_by_text = {}
|
||||
for (module, items) in modules.items():
|
||||
items.sort()
|
||||
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)
|
||||
|
||||
with open("strings_by_module.json", "w") as file:
|
||||
file.write(json.dumps(strings_by_module))
|
||||
with open("keys_by_text.json", "w") as file:
|
||||
file.write(json.dumps(keys_by_text))
|
|
@ -1,245 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: UTF-8 -*-
|
||||
|
||||
import os
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import polib
|
||||
import shutil
|
||||
import sys
|
||||
import subprocess
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from extract_po_string_diag import Ui_Dialog
|
||||
from fluent.syntax import parse, serialize
|
||||
from fluent.syntax.ast import Message, TextElement, Identifier, Pattern, Junk
|
||||
|
||||
# the templates folder inside the ftl repo
|
||||
repo_templates_dir = sys.argv[1]
|
||||
assert os.path.abspath(repo_templates_dir).endswith("templates")
|
||||
strings = json.load(open("strings.json" if len(sys.argv) < 3 else sys.argv[2]))
|
||||
plurals = json.load(open("plurals.json"))
|
||||
|
||||
|
||||
def transform_entry(entry, replacements):
|
||||
if isinstance(entry, str):
|
||||
return transform_string(entry, replacements)
|
||||
else:
|
||||
return [transform_string(e, replacements) for e in entry]
|
||||
|
||||
|
||||
def transform_string(msg, replacements):
|
||||
try:
|
||||
for (old, new) in replacements:
|
||||
msg = msg.replace(old, f"{new}")
|
||||
except ValueError:
|
||||
pass
|
||||
# strip leading/trailing whitespace
|
||||
return msg.strip()
|
||||
|
||||
|
||||
def plural_text(key, lang, translation):
|
||||
lang = re.sub("(_|-).*", "", lang)
|
||||
|
||||
# extract the variable - if there's more than one, use the first one
|
||||
var = re.findall(r"{(\$.*?)}", translation[0])
|
||||
if not len(var) == 1:
|
||||
print("multiple variables found, using first replacement")
|
||||
var = replacements[0][1].replace("{", "").replace("}", "")
|
||||
else:
|
||||
var = var[0]
|
||||
|
||||
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 key_from_search(search):
|
||||
return search.replace(" ", "-").replace("'", "")
|
||||
|
||||
|
||||
# add a non-pluralized message. works via fluent.syntax, so can automatically
|
||||
# indent, etc
|
||||
def add_simple_message(fname, key, message):
|
||||
orig = ""
|
||||
if os.path.exists(fname):
|
||||
with open(fname) as file:
|
||||
orig = file.read()
|
||||
|
||||
obj = parse(orig)
|
||||
for ent in obj.body:
|
||||
if isinstance(ent, Junk):
|
||||
raise Exception(f"file had junk! {fname} {ent}")
|
||||
obj.body.append(Message(Identifier(key), Pattern([TextElement(message)])))
|
||||
|
||||
modified = serialize(obj, with_junk=True)
|
||||
# escape leading dots
|
||||
modified = re.sub(r"(?ms)^( +)\.", '\\1{"."}', modified)
|
||||
|
||||
# ensure the resulting serialized file is valid by parsing again
|
||||
obj = parse(modified)
|
||||
for ent in obj.body:
|
||||
if isinstance(ent, Junk):
|
||||
raise Exception(f"introduced junk! {fname} {ent}")
|
||||
|
||||
# it's ok, write it out
|
||||
with open(fname, "w") as file:
|
||||
file.write(modified)
|
||||
|
||||
|
||||
def add_message(fname, key, translation):
|
||||
# simple, non-plural form?
|
||||
if isinstance(translation, str):
|
||||
add_simple_message(fname, key, translation)
|
||||
else:
|
||||
text = plural_text(key, lang, translation)
|
||||
open(fname, "a").write(text)
|
||||
|
||||
|
||||
def key_already_used(key: str) -> bool:
|
||||
return not subprocess.call(["grep", "-r", f"{key} =", repo_templates_dir])
|
||||
|
||||
|
||||
class Window(QDialog, Ui_Dialog):
|
||||
def __init__(self):
|
||||
QDialog.__init__(self)
|
||||
self.setupUi(self)
|
||||
|
||||
self.matched_strings = []
|
||||
|
||||
self.files = sorted(os.listdir(repo_templates_dir))
|
||||
self.filenames.addItems(self.files)
|
||||
|
||||
self.search.textChanged.connect(self.on_search)
|
||||
self.replacements.textChanged.connect(self.update_preview)
|
||||
self.key.textEdited.connect(self.update_preview)
|
||||
self.filenames.currentIndexChanged.connect(self.update_preview)
|
||||
self.searchMatches.currentItemChanged.connect(self.update_preview)
|
||||
self.replacementsTemplateButton.clicked.connect(self.on_template)
|
||||
self.addButton.clicked.connect(self.on_add)
|
||||
|
||||
def on_template(self):
|
||||
self.replacements.setText("%d={ $value }")
|
||||
# qt macos bug
|
||||
self.replacements.repaint()
|
||||
|
||||
def on_search(self):
|
||||
msgid_substring = self.search.text()
|
||||
self.key.setText(key_from_search(msgid_substring))
|
||||
|
||||
msgids = []
|
||||
exact_idx = None
|
||||
for n, id in enumerate(strings["en"].keys()):
|
||||
if msgid_substring.lower() in id.lower():
|
||||
msgids.append(id)
|
||||
|
||||
# is the ID an exact match?
|
||||
if msgid_substring in strings["en"]:
|
||||
exact_idx = n
|
||||
|
||||
self.matched_strings = msgids
|
||||
self.searchMatches.clear()
|
||||
self.searchMatches.addItems(self.matched_strings)
|
||||
if exact_idx is not None:
|
||||
self.searchMatches.setCurrentRow(exact_idx)
|
||||
elif self.matched_strings:
|
||||
self.searchMatches.setCurrentRow(0)
|
||||
|
||||
self.update_preview()
|
||||
|
||||
def update_preview(self):
|
||||
self.preview.clear()
|
||||
if not self.matched_strings:
|
||||
return
|
||||
|
||||
strings = self.get_adjusted_strings()
|
||||
key = self.get_key()
|
||||
self.preview.setPlainText(
|
||||
f"Key: {key}\n\n"
|
||||
+ "\n".join([f"{lang}: {value}" for (lang, value) in strings])
|
||||
)
|
||||
|
||||
# returns list of (lang, entry)
|
||||
def get_adjusted_strings(self):
|
||||
msgid = self.matched_strings[self.searchMatches.currentRow()]
|
||||
|
||||
# split up replacements
|
||||
replacements = []
|
||||
for repl in self.replacements.text().split(","):
|
||||
if not repl:
|
||||
continue
|
||||
replacements.append(repl.split("="))
|
||||
|
||||
to_insert = []
|
||||
for lang in strings.keys():
|
||||
entry = strings[lang].get(msgid)
|
||||
if not entry:
|
||||
continue
|
||||
entry = transform_entry(entry, replacements)
|
||||
if entry:
|
||||
to_insert.append((lang, entry))
|
||||
|
||||
return to_insert
|
||||
|
||||
def get_key(self):
|
||||
# add file as prefix to key
|
||||
prefix = os.path.splitext(self.filenames.currentText())[0]
|
||||
return f"{prefix}-{self.key.text()}"
|
||||
|
||||
def on_add(self):
|
||||
to_insert = self.get_adjusted_strings()
|
||||
key = self.get_key()
|
||||
if key_already_used(key):
|
||||
QMessageBox.warning(None, "Error", "Duplicate Key")
|
||||
return
|
||||
|
||||
# for each language's translation
|
||||
for lang, translation in to_insert:
|
||||
ftl_path = self.filename_for_lang(lang)
|
||||
add_message(ftl_path, key, translation)
|
||||
|
||||
if lang == "en":
|
||||
# copy file from repo into src
|
||||
srcdir = os.path.join(repo_templates_dir, "..", "..", "..")
|
||||
src_filename = os.path.join(srcdir, os.path.basename(ftl_path))
|
||||
shutil.copy(ftl_path, src_filename)
|
||||
|
||||
subprocess.check_call(
|
||||
f"cd {repo_templates_dir} && git add .. && git commit -m 'add {key}'",
|
||||
shell=True,
|
||||
)
|
||||
|
||||
self.preview.setPlainText(f"Added {key}.")
|
||||
self.preview.repaint()
|
||||
|
||||
def filename_for_lang(self, lang):
|
||||
fname = self.filenames.currentText()
|
||||
if lang == "en":
|
||||
return os.path.join(repo_templates_dir, fname)
|
||||
else:
|
||||
ftl_dir = os.path.join(repo_templates_dir, "..", lang)
|
||||
if not os.path.exists(ftl_dir):
|
||||
os.mkdir(ftl_dir)
|
||||
return os.path.join(ftl_dir, fname)
|
||||
|
||||
|
||||
print("Remember to pull-i18n before making changes.")
|
||||
if subprocess.check_output(f"git status --porcelain {repo_templates_dir}", shell=True):
|
||||
print("Repo has uncommitted changes.")
|
||||
sys.exit(1)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
window = Window()
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
|
@ -1,86 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'extract_po_string_diag.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.0
|
||||
#
|
||||
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
||||
# run again. Do not edit this file unless you know what you are doing.
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Dialog(object):
|
||||
def setupUi(self, Dialog):
|
||||
Dialog.setObjectName("Dialog")
|
||||
Dialog.resize(879, 721)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.gridLayout = QtWidgets.QGridLayout()
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.label_2 = QtWidgets.QLabel(Dialog)
|
||||
self.label_2.setObjectName("label_2")
|
||||
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
|
||||
self.filenames = QtWidgets.QComboBox(Dialog)
|
||||
self.filenames.setObjectName("filenames")
|
||||
self.gridLayout.addWidget(self.filenames, 1, 1, 1, 1)
|
||||
self.label_4 = QtWidgets.QLabel(Dialog)
|
||||
self.label_4.setObjectName("label_4")
|
||||
self.gridLayout.addWidget(self.label_4, 0, 0, 1, 1)
|
||||
self.replacementsTemplateButton = QtWidgets.QPushButton(Dialog)
|
||||
self.replacementsTemplateButton.setObjectName("replacementsTemplateButton")
|
||||
self.gridLayout.addWidget(self.replacementsTemplateButton, 4, 1, 1, 1)
|
||||
self.label = QtWidgets.QLabel(Dialog)
|
||||
self.label.setObjectName("label")
|
||||
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
|
||||
self.label_3 = QtWidgets.QLabel(Dialog)
|
||||
self.label_3.setObjectName("label_3")
|
||||
self.gridLayout.addWidget(self.label_3, 3, 0, 1, 1)
|
||||
self.replacements = QtWidgets.QLineEdit(Dialog)
|
||||
self.replacements.setObjectName("replacements")
|
||||
self.gridLayout.addWidget(self.replacements, 3, 1, 1, 1)
|
||||
self.key = QtWidgets.QLineEdit(Dialog)
|
||||
self.key.setObjectName("key")
|
||||
self.gridLayout.addWidget(self.key, 2, 1, 1, 1)
|
||||
self.search = QtWidgets.QLineEdit(Dialog)
|
||||
self.search.setObjectName("search")
|
||||
self.gridLayout.addWidget(self.search, 0, 1, 1, 1)
|
||||
self.verticalLayout.addLayout(self.gridLayout)
|
||||
self.label_5 = QtWidgets.QLabel(Dialog)
|
||||
self.label_5.setObjectName("label_5")
|
||||
self.verticalLayout.addWidget(self.label_5)
|
||||
self.searchMatches = QtWidgets.QListWidget(Dialog)
|
||||
self.searchMatches.setObjectName("searchMatches")
|
||||
self.verticalLayout.addWidget(self.searchMatches)
|
||||
self.label_6 = QtWidgets.QLabel(Dialog)
|
||||
self.label_6.setObjectName("label_6")
|
||||
self.verticalLayout.addWidget(self.label_6)
|
||||
self.preview = QtWidgets.QTextEdit(Dialog)
|
||||
self.preview.setObjectName("preview")
|
||||
self.verticalLayout.addWidget(self.preview)
|
||||
self.addButton = QtWidgets.QPushButton(Dialog)
|
||||
self.addButton.setObjectName("addButton")
|
||||
self.verticalLayout.addWidget(self.addButton)
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||
Dialog.setTabOrder(self.search, self.filenames)
|
||||
Dialog.setTabOrder(self.filenames, self.key)
|
||||
Dialog.setTabOrder(self.key, self.replacements)
|
||||
Dialog.setTabOrder(self.replacements, self.replacementsTemplateButton)
|
||||
Dialog.setTabOrder(self.replacementsTemplateButton, self.searchMatches)
|
||||
Dialog.setTabOrder(self.searchMatches, self.preview)
|
||||
Dialog.setTabOrder(self.preview, self.addButton)
|
||||
|
||||
def retranslateUi(self, Dialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
|
||||
self.label_2.setText(_translate("Dialog", "Fluent key suffix"))
|
||||
self.label_4.setText(_translate("Dialog", "Search:"))
|
||||
self.replacementsTemplateButton.setText(_translate("Dialog", "Template"))
|
||||
self.label.setText(_translate("Dialog", "Target file"))
|
||||
self.label_3.setText(_translate("Dialog", "Replacements"))
|
||||
self.label_5.setText(_translate("Dialog", "Matches"))
|
||||
self.label_6.setText(_translate("Dialog", "Preview"))
|
||||
self.addButton.setText(_translate("Dialog", "Add"))
|
|
@ -1,109 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>879</width>
|
||||
<height>721</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Fluent key suffix</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="filenames"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Search:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QPushButton" name="replacementsTemplateButton">
|
||||
<property name="text">
|
||||
<string>Template</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Target file</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Replacements</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="replacements"/>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="key"/>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="search"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Matches</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="searchMatches"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Preview</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextEdit" name="preview"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="addButton">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>search</tabstop>
|
||||
<tabstop>filenames</tabstop>
|
||||
<tabstop>key</tabstop>
|
||||
<tabstop>replacements</tabstop>
|
||||
<tabstop>replacementsTemplateButton</tabstop>
|
||||
<tabstop>searchMatches</tabstop>
|
||||
<tabstop>preview</tabstop>
|
||||
<tabstop>addButton</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -1,50 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import glob, re, json, stringcase
|
||||
|
||||
files = (
|
||||
# glob.glob("../../pylib/**/*.py", recursive=True) +
|
||||
glob.glob("../../qt/**/*.py", recursive=True)
|
||||
)
|
||||
string_re = re.compile(r'_\(\s*(".*?")\s*\)', re.DOTALL)
|
||||
|
||||
map = json.load(open("keys_by_text.json"))
|
||||
|
||||
# unused or missing strings
|
||||
blacklist = {
|
||||
"Label1",
|
||||
"After pressing OK, you can choose which tags to include.",
|
||||
"Filter/Cram",
|
||||
# previewer.py needs updating to fix these
|
||||
"Shortcut key: R",
|
||||
"Shortcut key: B",
|
||||
}
|
||||
|
||||
|
||||
def repl(m):
|
||||
# the argument may consistent of multiple strings that need merging together
|
||||
text = eval("(" + m.group(1) + ")")
|
||||
print(f"text is `{text}`")
|
||||
|
||||
if text in blacklist:
|
||||
return m.group(0)
|
||||
|
||||
(module, key) = map[text]
|
||||
screaming = stringcase.constcase(key)
|
||||
print(screaming)
|
||||
|
||||
if "%d" in text or "%s" in text:
|
||||
# replace { $val } with %s for compat with old code
|
||||
return f'tr(TR.{screaming}, val="%s")'
|
||||
|
||||
return f"tr(TR.{screaming})"
|
||||
|
||||
|
||||
for file in files:
|
||||
buf = open(file).read()
|
||||
buf2 = string_re.sub(repl, buf)
|
||||
if buf != buf2:
|
||||
lines = buf2.split("\n")
|
||||
lines.insert(3, "from aqt.utils import tr, TR")
|
||||
buf2 = "\n".join(lines)
|
||||
open(file, "w").write(buf2)
|
|
@ -1,100 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from fluent.syntax import parse, serialize
|
||||
from fluent.syntax.ast import Message, TextElement, Identifier, Pattern, Junk
|
||||
|
||||
core = "../../../anki-i18n/core/core"
|
||||
qt = "../../../anki-i18n/qtftl/desktop"
|
||||
|
||||
qt_modules = {"about", "qt-accel", "addons", "qt-misc"}
|
||||
|
||||
modules = json.load(open("strings_by_module.json"))
|
||||
translations = json.load(open("strings.json"))
|
||||
|
||||
# # fixme:
|
||||
# addons addons-downloaded-fnames Downloaded %(fname)s
|
||||
# addons addons-downloading-adbd-kb02fkb Downloading %(a)d/%(b)d (%(kb)0.2fKB)...
|
||||
# addons addons-error-downloading-ids-errors Error downloading <i>%(id)s</i>: %(error)s
|
||||
# addons addons-error-installing-bases-errors Error installing <i>%(base)s</i>: %(error)s
|
||||
# addons addons-important-as-addons-are-programs-downloaded <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
|
||||
# addons addons-installed-names Installed %(name)s
|
||||
# addons addons-the-following-addons-are-incompatible-with The following add-ons are incompatible with %(name)s and have been disabled: %(found)s
|
||||
# card-templates card-templates-delete-the-as-card-type-and Delete the '%(a)s' card type, and its %(b)s?
|
||||
# browsing browsing-found-as-across-bs Found %(a)s across %(b)s.
|
||||
# browsing browsing-nd-names %(n)d: %(name)s
|
||||
# importing importing-rows-had-num1d-fields-expected-num2d '%(row)s' had %(num1)d fields, expected %(num2)d
|
||||
# about about-written-by-damien-elmes-with-patches Written by Damien Elmes, with patches, translation, testing and design from:<p>%(cont)s
|
||||
# media media-recordingtime Recording...<br>Time: %0.1f
|
||||
# addons-unknown-error = Unknown error: {}
|
||||
|
||||
# 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", " ")
|
||||
return text
|
||||
|
||||
|
||||
def check_parses(path: str):
|
||||
# make sure the file parses
|
||||
with open(path) as file:
|
||||
orig = file.read()
|
||||
obj = parse(orig)
|
||||
for ent in obj.body:
|
||||
if isinstance(ent, Junk):
|
||||
raise Exception(f"file had junk! {path} {ent}")
|
||||
|
||||
|
||||
for module, items in modules.items():
|
||||
if module in qt_modules:
|
||||
folder = qt
|
||||
else:
|
||||
folder = core
|
||||
|
||||
if not module.startswith("statistics"):
|
||||
continue
|
||||
|
||||
# templates
|
||||
path = os.path.join(folder, "templates", module + ".ftl")
|
||||
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")
|
||||
|
||||
check_parses(path)
|
||||
|
||||
# translations
|
||||
for (lang, map) in translations.items():
|
||||
if lang == "en":
|
||||
continue
|
||||
|
||||
out = []
|
||||
for (key, text) in items:
|
||||
if text in map:
|
||||
out.append((key, transform_text(map[text])))
|
||||
|
||||
if out:
|
||||
path = os.path.join(folder, lang, module + ".ftl")
|
||||
dir = os.path.dirname(path)
|
||||
if not os.path.exists(dir):
|
||||
os.mkdir(dir)
|
||||
|
||||
print(path)
|
||||
with open(path, "a", encoding="utf8") as file:
|
||||
for (key, text) in out:
|
||||
file.write(f"{key} = {text}\n")
|
||||
|
||||
check_parses(path)
|
15
repos.bzl
15
repos.bzl
|
@ -126,9 +126,6 @@ def register_repos():
|
|||
qtftl_i18n_commit = "9909cfa4386288e686b2336b3b1048b7ee1bb194"
|
||||
qtftl_i18n_shallow_since = "1605664969 +1000"
|
||||
|
||||
qtpo_i18n_commit = "872d7f0f6bde52577e8fc795dd85699b0eeb97d5"
|
||||
qtpo_i18n_shallow_since = "1605564627 +0000"
|
||||
|
||||
i18n_build_content = """
|
||||
filegroup(
|
||||
name = "files",
|
||||
|
@ -166,15 +163,3 @@ exports_files(["l10n.toml"])
|
|||
shallow_since = qtftl_i18n_shallow_since,
|
||||
remote = "https://github.com/ankitects/anki-desktop-ftl",
|
||||
)
|
||||
|
||||
new_git_repository(
|
||||
name = "aqt_po",
|
||||
build_file_content = """
|
||||
exports_files(glob(["**/*.pot", "**/*.po"]),
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
""",
|
||||
commit = qtpo_i18n_commit,
|
||||
shallow_since = qtpo_i18n_shallow_since,
|
||||
remote = "https://github.com/ankitects/anki-desktop-i18n",
|
||||
)
|
||||
|
|
2
run
2
run
|
@ -11,7 +11,7 @@ run_mac() {
|
|||
# so we need to copy the files into a working folder before running on a Mac.
|
||||
workspace=$(dirname $0)
|
||||
bazel build //qt:runanki && \
|
||||
rsync -avPL --exclude=anki/external --exclude=__pycache__ --delete \
|
||||
rsync -aiL --no-times --exclude=anki/external --exclude=__pycache__ --delete \
|
||||
$workspace/bazel-bin/qt/runanki* $workspace/bazel-copy/ && \
|
||||
$workspace/bazel-copy/runanki $*
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue