mirror of
https://github.com/zulip/zulip.git
synced 2025-11-23 16:01:24 +00:00
As of commitcff40c557b(#9300), these files are no longer served directly to the browser. Disentangle them from the static asset pipeline so we can refactor it without worrying about them. This has the side effect of eliminating the accidental duplication of translation data via hash-naming in our release tarballs. This reverts commitb546391f0b(#1148). Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
168 lines
6.0 KiB
Python
168 lines
6.0 KiB
Python
|
|
import json
|
|
import os
|
|
import polib
|
|
import re
|
|
import ujson
|
|
from subprocess import CalledProcessError, check_output
|
|
from typing import Any, Dict, List
|
|
|
|
from django.conf import settings
|
|
from django.conf.locale import LANG_INFO
|
|
from django.core.management.base import CommandParser
|
|
from django.core.management.commands import compilemessages
|
|
from django.utils.translation.trans_real import to_language
|
|
|
|
from zerver.lib.i18n import with_language
|
|
|
|
class Command(compilemessages.Command):
|
|
|
|
def add_arguments(self, parser: CommandParser) -> None:
|
|
super().add_arguments(parser)
|
|
|
|
parser.add_argument(
|
|
'--strict', '-s',
|
|
action='store_true',
|
|
default=False,
|
|
help='Stop execution in case of errors.')
|
|
|
|
def handle(self, *args: Any, **options: Any) -> None:
|
|
super().handle(*args, **options)
|
|
self.strict = options['strict']
|
|
self.extract_language_options()
|
|
self.create_language_name_map()
|
|
|
|
def create_language_name_map(self) -> None:
|
|
join = os.path.join
|
|
deploy_root = settings.DEPLOY_ROOT
|
|
path = join(deploy_root, 'locale', 'language_options.json')
|
|
output_path = join(deploy_root, 'locale', 'language_name_map.json')
|
|
|
|
with open(path, 'r') as reader:
|
|
languages = ujson.load(reader)
|
|
lang_list = []
|
|
for lang_info in languages['languages']:
|
|
lang_info['name'] = lang_info['name_local']
|
|
del lang_info['name_local']
|
|
lang_list.append(lang_info)
|
|
|
|
lang_list.sort(key=lambda lang: lang['name'])
|
|
|
|
with open(output_path, 'w') as output_file:
|
|
ujson.dump({'name_map': lang_list}, output_file, indent=4, sort_keys=True)
|
|
output_file.write('\n')
|
|
|
|
def get_po_filename(self, locale_path: str, locale: str) -> str:
|
|
po_template = '{}/{}/LC_MESSAGES/django.po'
|
|
return po_template.format(locale_path, locale)
|
|
|
|
def get_json_filename(self, locale_path: str, locale: str) -> str:
|
|
return "{}/{}/translations.json".format(locale_path, locale)
|
|
|
|
def get_name_from_po_file(self, po_filename: str, locale: str) -> str:
|
|
lang_name_re = re.compile(r'"Language-Team: (.*?) \(')
|
|
with open(po_filename, 'r') as reader:
|
|
result = lang_name_re.search(reader.read())
|
|
if result:
|
|
try:
|
|
return result.group(1)
|
|
except Exception:
|
|
print("Problem in parsing {}".format(po_filename))
|
|
raise
|
|
else:
|
|
raise Exception("Unknown language %s" % (locale,))
|
|
|
|
def get_locales(self) -> List[str]:
|
|
tracked_files = check_output(['git', 'ls-files', 'locale'])
|
|
tracked_files = tracked_files.decode().split()
|
|
regex = re.compile(r'locale/(\w+)/LC_MESSAGES/django.po')
|
|
locales = ['en']
|
|
for tracked_file in tracked_files:
|
|
matched = regex.search(tracked_file)
|
|
if matched:
|
|
locales.append(matched.group(1))
|
|
|
|
return locales
|
|
|
|
def extract_language_options(self) -> None:
|
|
locale_path = "{}/locale".format(settings.DEPLOY_ROOT)
|
|
output_path = "{}/language_options.json".format(locale_path)
|
|
|
|
data = {'languages': []} # type: Dict[str, List[Dict[str, Any]]]
|
|
|
|
try:
|
|
locales = self.get_locales()
|
|
except CalledProcessError:
|
|
# In case we are not under a Git repo, fallback to getting the
|
|
# locales using listdir().
|
|
locales = os.listdir(locale_path)
|
|
locales.append('en')
|
|
locales = list(set(locales))
|
|
|
|
for locale in locales:
|
|
if locale == 'en':
|
|
data['languages'].append({
|
|
'name': 'English',
|
|
'name_local': 'English',
|
|
'code': 'en',
|
|
'locale': 'en',
|
|
})
|
|
continue
|
|
|
|
lc_messages_path = os.path.join(locale_path, locale, 'LC_MESSAGES')
|
|
if not os.path.exists(lc_messages_path):
|
|
# Not a locale.
|
|
continue
|
|
|
|
info = {} # type: Dict[str, Any]
|
|
code = to_language(locale)
|
|
percentage = self.get_translation_percentage(locale_path, locale)
|
|
try:
|
|
name = LANG_INFO[code]['name']
|
|
name_local = LANG_INFO[code]['name_local']
|
|
except KeyError:
|
|
# Fallback to getting the name from PO file.
|
|
filename = self.get_po_filename(locale_path, locale)
|
|
name = self.get_name_from_po_file(filename, locale)
|
|
name_local = with_language(name, code)
|
|
|
|
info['name'] = name
|
|
info['name_local'] = name_local
|
|
info['code'] = code
|
|
info['locale'] = locale
|
|
info['percent_translated'] = percentage
|
|
data['languages'].append(info)
|
|
|
|
with open(output_path, 'w') as writer:
|
|
json.dump(data, writer, indent=2, sort_keys=True)
|
|
writer.write('\n')
|
|
|
|
def get_translation_percentage(self, locale_path: str, locale: str) -> int:
|
|
|
|
# backend stats
|
|
po = polib.pofile(self.get_po_filename(locale_path, locale))
|
|
not_translated = len(po.untranslated_entries())
|
|
total = len(po.translated_entries()) + not_translated
|
|
|
|
# frontend stats
|
|
with open(self.get_json_filename(locale_path, locale)) as reader:
|
|
for key, value in ujson.load(reader).items():
|
|
total += 1
|
|
if value == '':
|
|
not_translated += 1
|
|
|
|
# mobile stats
|
|
with open(os.path.join(locale_path, 'mobile_info.json')) as mob:
|
|
mobile_info = ujson.load(mob)
|
|
try:
|
|
info = mobile_info[locale]
|
|
except KeyError:
|
|
if self.strict:
|
|
raise
|
|
info = {'total': 0, 'not_translated': 0}
|
|
|
|
total += info['total']
|
|
not_translated += info['not_translated']
|
|
|
|
return (total - not_translated) * 100 // total
|