19f1c98025
1. Documented on README.development how to setup the environment for Windows. 2. Fixed qt/ts/package.json not working due usage of ; instead of && 3. Fixed copy-qt-files rsync using Windows paths instead of Unix ones 4. Fixed Makefile's using Windows Linux Subsystem bash instead of the Cygwin one. 5. Ensured running the correct pip module by using python -m pip instead of just pip. 6. Fixed Makefiles using Windows `find` command, instead of the Cygwin's one (POSIX find). 7. Fixed pyenv sourcing/activate using /pyevn/bin/ instead of /python/Scripts/ on Windows. 8. Fixed pyaudio not installing/linking with portaudio on Windows by installing for a patched fork at evandroforks/pyaudio 9. Forked and fixed portaudio not building with Visual Studio 2017 or superior and added the reference for the patched fork on README.development at evandroforks/portaudio.
182 lines
5.1 KiB
Python
182 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: UTF-8 -*-
|
|
import os
|
|
import json
|
|
import re
|
|
import sys
|
|
import polib
|
|
from fluent.syntax import parse, serialize
|
|
from fluent.syntax.ast import Message, TextElement, Identifier, Pattern
|
|
|
|
# extract a translated string from the gettext catalogs and insert it into ftl
|
|
# eg:
|
|
# $ python extract-po-string.py /path/to/templates/media-check.ftl delete-unused "Delete Unused Media" ""
|
|
# $ python extract-po-string.py /path/to/templates/media-check.ftl delete-unused "%(a)s %(b)s" "%(a)s=$val1,%(b)s=$val2"
|
|
#
|
|
# NOTE: the English text is written into the templates folder of the repo, so must be copied
|
|
# into Anki's source tree
|
|
|
|
ftl_filename, key, msgid, repls = sys.argv[1:]
|
|
|
|
# split up replacements
|
|
replacements = []
|
|
for repl in repls.split(","):
|
|
if not repl:
|
|
continue
|
|
replacements.append(repl.split("="))
|
|
|
|
# add file as prefix to key
|
|
prefix = os.path.splitext(os.path.basename(ftl_filename))[0]
|
|
key = f"{prefix}-{key}"
|
|
|
|
# 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:
|
|
msg = entry.msgstr
|
|
if replacements:
|
|
for (old, new) in replacements:
|
|
msg = msg.replace(old, f"{{{new}}}")
|
|
return msg
|
|
# 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:
|
|
if replacements:
|
|
for (old, new) in replacements:
|
|
msg = msg.replace(old, f"{{{new}}}")
|
|
adjusted.append(msg)
|
|
if len(adjusted) > 1 and adjusted[0]:
|
|
return adjusted
|
|
else:
|
|
if adjusted[0]:
|
|
return adjusted[0]
|
|
return None
|
|
|
|
|
|
# start by checking the .pot file for the message
|
|
base = "i18n/po/desktop"
|
|
pot = os.path.join(base, "anki.pot")
|
|
pot_cat = polib.pofile(pot)
|
|
catalogs = []
|
|
|
|
# is the ID an exact match?
|
|
resolved_entry = None
|
|
for entry in pot_cat:
|
|
if entry.msgid == msgid:
|
|
resolved_entry = entry
|
|
|
|
# try a substring match, but make sure it doesn't match
|
|
# multiple items
|
|
if resolved_entry is None:
|
|
for entry in pot_cat:
|
|
if msgid in entry.msgid:
|
|
if resolved_entry is not None:
|
|
print("aborting, matched both", resolved_entry.msgid)
|
|
print("and", entry.msgid)
|
|
sys.exit(1)
|
|
resolved_entry = entry
|
|
|
|
if resolved_entry is None:
|
|
print("no IDs matched")
|
|
sys.exit(1)
|
|
|
|
msgid = resolved_entry.msgid
|
|
print("matched id", msgid)
|
|
|
|
print("loading translations...")
|
|
# load the translations from each language
|
|
langs = [d for d in os.listdir(base) if d != "anki.pot"]
|
|
for lang in langs:
|
|
po_path = os.path.join(base, lang, "anki.po")
|
|
cat = polib.pofile(po_path)
|
|
catalogs.append((lang, cat))
|
|
|
|
to_insert = []
|
|
for (lang, cat) in catalogs:
|
|
for entry in cat:
|
|
if entry.msgid == msgid:
|
|
translation = get_msgstr(entry)
|
|
if translation:
|
|
print(f"{lang} had translation {translation}")
|
|
to_insert.append((lang, translation))
|
|
else:
|
|
print(f"{lang} had no translation")
|
|
break
|
|
|
|
plurals = json.load(open("i18n/plurals.json"))
|
|
|
|
|
|
def plural_text(key, lang, translation):
|
|
lang = re.sub("(_|-).*", "", lang)
|
|
|
|
# extract the variable - there should be only one
|
|
var = re.findall(r"{(\$.*?)}", translation[0])
|
|
if not len(var) == 1:
|
|
return None
|
|
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
|
|
|
|
|
|
# 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):
|
|
orig = open(fname).read()
|
|
|
|
obj = parse(orig)
|
|
obj.body.append(Message(Identifier(key), Pattern([TextElement(message)])))
|
|
|
|
modified = serialize(obj)
|
|
open(fname, "w").write(modified)
|
|
|
|
|
|
def add_message(fname, key, translation):
|
|
# simple, non-plural form?
|
|
if isinstance(translation, str):
|
|
add_simple_message(fname, key, translation)
|
|
else:
|
|
raise
|
|
return plural_text(key, lang, translation)
|
|
|
|
|
|
print()
|
|
input("proceed? ctrl+c to abort")
|
|
|
|
# add template first
|
|
if resolved_entry.msgid_plural:
|
|
original = [resolved_entry.msgid, resolved_entry.msgid_plural]
|
|
else:
|
|
original = resolved_entry.msgid
|
|
|
|
add_message(ftl_filename, key, original)
|
|
|
|
# the each language's translation
|
|
for lang, translation in to_insert:
|
|
ftl_path = ftl_filename.replace("templates", lang)
|
|
ftl_dir = os.path.dirname(ftl_path)
|
|
|
|
if not os.path.exists(ftl_dir):
|
|
os.mkdir(ftl_dir)
|
|
|
|
add_message(ftl_path, key, translation)
|
|
|
|
print("done")
|