Files
SomberNight 3afa2fcdf3 locale: gui: show translation completion percentage in language names
In the GUIs, on the language-select screen, show e.g.
  Czech (100%), Danish (13%), Dutch (54%)
instead of
  Czech, Danish, Dutch

- we count the source strings when creating the .pot PO-template file
  and add an "X-Electrum-SourceStringCount" header to it, in the push_locale.py script that uploads the .pot file to crowdin.
  - later, when we run electrum-locale/update.py to download the translations in .po files, these files will also contain the same header.
  - then when the build_locale.sh script compiles those .po files, we can read the header and use it to populate a new "stats.json" file
    that we place in electrum/locale/locale/ and bundle in the all release binaries/distributables.
    - stats.json also includes the number of translated strings for each lang
- at runtime we simply read stats.json and use the values to calculate the percentages
  - a prior implementation did not pre-calc stats.json but did all calculations at runtime (by opening all .mo translations)
    - however that was deemed to slow, hence the build-time pre-calc
      - runtime calc took 40 ms on my laptop, so I guess it could easily take 10x that on an old phone
- just as we have always been very tolerant of any locale files or even the whole locale/ dir missing, we also tolerate stats.json missing
2026-02-21 03:40:09 +00:00

73 lines
2.6 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright (C) 2026 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
#
#
# This generates a 'stats.json' file containing some statistics about translation completeness.
import gettext
import glob
import json
import os
PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
LOCALE_DIR = os.path.join(PROJECT_ROOT, "electrum", "locale", "locale")
if __name__ == '__main__':
catalog_size = {} # type: dict[str, int]
source_string_count = None
# - calc stats
files_list = glob.glob(f"{LOCALE_DIR}/*/LC_MESSAGES/*.mo")
for fname in files_list:
lang_code = os.path.basename(os.path.dirname(os.path.dirname(fname)))
try:
t = gettext.translation('electrum', LOCALE_DIR, languages=[lang_code])
except OSError as e:
raise Exception(f"cannot find or parse .mo file matching {fname!r}") from e
# calc catalog size of translated strings
catalog_size[lang_code] = len(t._catalog)
# same SourceStringCount header should be present in all .mo files:
t_info = t.info()
try:
ss_cnt = int(t_info["x-electrum-sourcestringcount"])
except Exception as e:
raise Exception(
f"missing or malformed 'x-electrum-sourcestringcount' header, for {lang_code!r}.\n"
f"found {t_info}"
) from e
if source_string_count is None:
source_string_count = ss_cnt
elif source_string_count != ss_cnt:
raise Exception(
f"inconsistent 'x-electrum-sourcestringcount' headers! "
f"prev_cnt={source_string_count}, new_cnt={ss_cnt} (for lang={lang_code})")
# - convert to json data. example:
# {
# "source_string_count": 9999,
# "translations": {
# "de_DE": {
# "string_count": 400,
# },
# ...
# }
# }
json_data = {
"source_string_count": source_string_count,
"translations": {},
}
for lang_code in catalog_size:
json_data["translations"][lang_code] = {}
json_data["translations"][lang_code]["string_count"] = catalog_size[lang_code]
# - write json to disk
with open(f"{LOCALE_DIR}/stats.json", "w", encoding="utf-8") as f:
json_str = json.dumps(
json_data,
indent=4,
sort_keys=True
)
f.write(json_str)
print(f"done. created file '{LOCALE_DIR}/stats.json'")