mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -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
|
set -x
|
||||||
sudo apt update
|
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 && \
|
curl -L https://github.com/bazelbuild/bazelisk/releases/download/v1.7.4/bazelisk-linux-amd64 -o ./bazel && \
|
||||||
chmod +x ./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**:
|
**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.
|
The 'find' utility is 'findutils' on Debian.
|
||||||
|
|
|
@ -14,7 +14,7 @@ Install Homebrew from <https://brew.sh/>
|
||||||
Then install deps:
|
Then install deps:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ brew install rsync gettext bazelisk
|
$ brew install rsync bazelisk
|
||||||
```
|
```
|
||||||
|
|
||||||
**Install Python 3.8**:
|
**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:
|
After installation completes, run msys2, and run the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ pacman -S git gettext
|
$ pacman -S git
|
||||||
```
|
```
|
||||||
|
|
||||||
**Bazelisk**:
|
**Bazelisk**:
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# 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
|
from __future__ import annotations
|
||||||
|
|
||||||
import gettext
|
|
||||||
import re
|
import re
|
||||||
from typing import Optional, Union
|
from typing import Optional
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
|
|
||||||
|
@ -142,11 +139,6 @@ def lang_to_disk_lang(lang: str) -> str:
|
||||||
# the currently set interface language
|
# the currently set interface language
|
||||||
currentLang = "en"
|
currentLang = "en"
|
||||||
|
|
||||||
# the current gettext translation catalog
|
|
||||||
current_catalog: Optional[
|
|
||||||
Union[gettext.NullTranslations, gettext.GNUTranslations]
|
|
||||||
] = None
|
|
||||||
|
|
||||||
# the current Fluent translation instance
|
# the current Fluent translation instance
|
||||||
current_i18n: Optional[anki.rsbackend.RustBackend] = None
|
current_i18n: Optional[anki.rsbackend.RustBackend] = None
|
||||||
|
|
||||||
|
@ -155,12 +147,15 @@ locale_folder = ""
|
||||||
|
|
||||||
|
|
||||||
def _(str: str) -> str:
|
def _(str: str) -> str:
|
||||||
if current_catalog:
|
print(f"gettext _() is deprecated: {str}")
|
||||||
return current_catalog.gettext(str)
|
|
||||||
else:
|
|
||||||
return 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:
|
def tr_legacyglobal(*args, **kwargs) -> str:
|
||||||
"Should use col.tr() instead."
|
"Should use col.tr() instead."
|
||||||
if current_i18n:
|
if current_i18n:
|
||||||
|
@ -169,26 +164,10 @@ def tr_legacyglobal(*args, **kwargs) -> str:
|
||||||
return "tr_legacyglobal() called without active backend"
|
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:
|
def set_lang(lang: str, locale_dir: str) -> None:
|
||||||
global currentLang, current_catalog, current_i18n, locale_folder
|
global currentLang, current_i18n, locale_folder
|
||||||
gettext_dir = locale_dir
|
|
||||||
ftl_dir = locale_dir
|
|
||||||
|
|
||||||
currentLang = lang
|
currentLang = lang
|
||||||
current_catalog = gettext.translation(
|
current_i18n = anki.rsbackend.RustBackend(ftl_folder=locale_folder, langs=[lang])
|
||||||
"anki", gettext_dir, languages=[lang], fallback=True
|
|
||||||
)
|
|
||||||
|
|
||||||
current_i18n = anki.rsbackend.RustBackend(ftl_folder=ftl_dir, langs=[lang])
|
|
||||||
|
|
||||||
locale_folder = locale_dir
|
locale_folder = locale_dir
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import argparse
|
import argparse
|
||||||
import builtins
|
import builtins
|
||||||
import getpass
|
import getpass
|
||||||
import gettext
|
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
@ -167,7 +166,7 @@ dialogs = DialogManager()
|
||||||
# Language handling
|
# Language handling
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# Qt requires its translator to be installed before any GUI widgets are
|
# 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.
|
# translated shortcuts will not work.
|
||||||
|
|
||||||
# A reference to the Qt translator needs to be held to prevent it from
|
# 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 = force or pm.meta["defaultLang"]
|
||||||
lang = anki.lang.lang_to_disk_lang(lang)
|
lang = anki.lang.lang_to_disk_lang(lang)
|
||||||
|
|
||||||
# load gettext catalog
|
# set active language
|
||||||
ldir = locale_dir()
|
ldir = locale_dir()
|
||||||
anki.lang.set_lang(lang, ldir)
|
anki.lang.set_lang(lang, ldir)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "data",
|
name = "data",
|
||||||
srcs = [
|
srcs = [
|
||||||
"//qt/aqt/data/locale",
|
|
||||||
"//qt/aqt/data/web",
|
"//qt/aqt/data/web",
|
||||||
],
|
],
|
||||||
visibility = ["//qt:__subpackages__"],
|
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
|
# included implicitly in the past, and relied upon by some add-ons
|
||||||
import cgi
|
import cgi
|
||||||
import decimal
|
import decimal
|
||||||
|
import gettext
|
||||||
|
|
||||||
# useful for add-ons
|
# useful for add-ons
|
||||||
import logging
|
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_commit = "9909cfa4386288e686b2336b3b1048b7ee1bb194"
|
||||||
qtftl_i18n_shallow_since = "1605664969 +1000"
|
qtftl_i18n_shallow_since = "1605664969 +1000"
|
||||||
|
|
||||||
qtpo_i18n_commit = "872d7f0f6bde52577e8fc795dd85699b0eeb97d5"
|
|
||||||
qtpo_i18n_shallow_since = "1605564627 +0000"
|
|
||||||
|
|
||||||
i18n_build_content = """
|
i18n_build_content = """
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "files",
|
name = "files",
|
||||||
|
@ -166,15 +163,3 @@ exports_files(["l10n.toml"])
|
||||||
shallow_since = qtftl_i18n_shallow_since,
|
shallow_since = qtftl_i18n_shallow_since,
|
||||||
remote = "https://github.com/ankitects/anki-desktop-ftl",
|
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.
|
# so we need to copy the files into a working folder before running on a Mac.
|
||||||
workspace=$(dirname $0)
|
workspace=$(dirname $0)
|
||||||
bazel build //qt:runanki && \
|
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-bin/qt/runanki* $workspace/bazel-copy/ && \
|
||||||
$workspace/bazel-copy/runanki $*
|
$workspace/bazel-copy/runanki $*
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue