Commit 5cd432e4 authored by Yuri Konotopov's avatar Yuri Konotopov

i18n: added damned-lies compatible translation workflow support.

1. Implemented damned-lies compatible translation workflow with single
gettext template in the "po" directory"
2. Implemented Mustache templates strings extraction in makemessages.
3. Implemented Django-compatible compilation of translations from single
gettext template to "django.po" and "djangojs.po"

See-Also: !9
parent b81d74c7
......@@ -99,6 +99,7 @@ Once you've done that, proceed with the database migrations:
::
$ python manage.py migrate
$ python manage.py compilemessages
$ python mange.py createsuperuser --username=joe --email=joe@email.com
After above steps your database should be initialized and almost ready to run.
......
......@@ -7,6 +7,10 @@ ENV PYTHONUNBUFFERED=1 \
GPG_KEY=08E2400FF7FE8FEDE3ACB52818147B073BAD2B07
RUN set -ex \
&& apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
gettext \
&& rm -r /var/lib/apt/lists/* \
&& pip install Sphinx \
&& wget -O xapian-core.tar.xz "https://oligarchy.co.uk/xapian/$XAPIAN_VERSION/xapian-core-$XAPIAN_VERSION.tar.xz" \
&& wget -O xapian-core.tar.xz.asc "https://oligarchy.co.uk/xapian/$XAPIAN_VERSION/xapian-core-$XAPIAN_VERSION.tar.xz.asc" \
......@@ -68,4 +72,5 @@ RUN set -ex \
&& chown www-data:root /extensions-web/wsgi.ini \
&& pip install -r requirements.txt \
&& pip install mysqlclient \
&& pip install uWSGI
&& pip install uWSGI \
&& EGO_SECRET_KEY=- python manage.py compilemessages
......@@ -4,6 +4,7 @@ django-contrib-comments == 1.9.0
django-registration == 2.5.2
Pygments >= 1.4
pillow >= 2.0.0
polib >= 1.1.0
chardet >= 2.2.1
dj-database-url
dj-email-url
"""
GNOME Shell extensions repository
Copyright (C) 2019 Yuri Konotopov <ykonotopov@gnome.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
"""
from django.conf import settings
from pathlib import Path
class MessagesCommand:
metadata_domain_prefix = 'extensions-web-domain-'
po_path = Path(settings.BASE_DIR).joinpath('po')
linguas_path = Path(po_path).joinpath('LINGUAS')
locale_path = Path(settings.SITE_ROOT).joinpath("locale")
def check_po_directory(self):
Path(self.po_path).mkdir(parents=True, exist_ok=True)
Path(self.linguas_path).touch(exist_ok=True)
def create_locale_directory(self):
self.locale_path.mkdir(parents=True, exist_ok=True)
"""
GNOME Shell extensions repository
Copyright (C) 2019 Yuri Konotopov <ykonotopov@gnome.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
"""
from django.core.management.commands.compilemessages import Command as CompileMessagesCommand
from sweettooth.core.management.commands import MessagesCommand
from pathlib import Path
import polib
class Command(CompileMessagesCommand, MessagesCommand):
def handle(self, *args, **options):
self.check_po_directory()
self.create_locale_directory()
self.copy_translations()
super().handle(*args, **options)
def copy_translations(self):
with open(self.linguas_path, 'r') as file:
for line in file:
lang = line.strip()
po_path = Path(self.po_path, lang + '.po')
if not lang or not po_path.is_file():
continue
self.stdout.write('processing language %s\n' % lang)
source = polib.pofile(po_path)
django = polib.POFile()
djangojs = polib.POFile()
django.metadata = source.metadata
djangojs.metadata = source.metadata
for entry in source:
for occurrence, line in entry.occurrences:
if occurrence.startswith(self.metadata_domain_prefix):
domain = occurrence[len(self.metadata_domain_prefix):]
if domain == 'django':
django.append(entry)
elif domain == 'djangojs':
djangojs.append(entry)
lang_path = self.locale_path.joinpath(lang, 'LC_MESSAGES')
lang_path.mkdir(parents=True, exist_ok=True)
django.save(str(lang_path.joinpath("django.po")))
djangojs.save(str(lang_path.joinpath("djangojs.po")))
"""
GNOME Shell extensions repository
Copyright (C) 2019 Yuri Konotopov <ykonotopov@gnome.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
"""
from datetime import datetime
from django.conf import settings
from django.core.management.commands.makemessages import Command as MakeMessagesCommand
from html.parser import HTMLParser
from pathlib import Path
import polib
import pytz
from sweettooth.core.management.commands import MessagesCommand
class GettextParser(HTMLParser):
in_gettext = False
po = None
file = None
def __init__(self, po):
super().__init__(convert_charrefs=True)
self.po = po
def set_file(self, file):
self.file = file
def handle_starttag(self, tag, attrs):
if tag.lower() == 'x-gettext':
self.in_gettext = True
else:
self.in_gettext = False
def handle_endtag(self, tag):
self.in_gettext = False
def handle_data(self, data):
if self.in_gettext:
(line, offset) = self.getpos()
self.po.append(polib.POEntry(msgid=data, occurrences=[(self.file, line)]))
class Command(MakeMessagesCommand, MessagesCommand):
def handle(self, *args, **options):
options['keep_pot'] = True
self.check_po_directory()
self.create_locale_directory()
for domain in ('django', 'djangojs'):
self.locale_paths = []
options['domain'] = domain
super().handle(*args, **options)
self.create_po_template()
for domain in ('django', 'djangojs'):
self.domain = domain
self.remove_potfiles()
def build_potfiles(self):
potfiles = super().build_potfiles()
if self.domain == 'djangojs':
if len(potfiles) > 1:
# We do not support multiple locale dirs
raise NotImplementedError("Multiple locale dirs are not supported")
po = polib.pofile(potfiles[0], check_for_duplicates = True)
self.search_mustache_texts(po)
po.sort()
po.save(potfiles[0])
return potfiles
def search_mustache_texts(self, po):
extensions = self.extensions
self.extensions = {'.mst'}
parser = GettextParser(po)
file_list = self.find_files(Path(settings.SITE_ROOT).joinpath('static', 'js'))
for translatable_file in file_list:
with open(translatable_file.path, 'r') as file:
parser.set_file(translatable_file.path.replace(settings.BASE_DIR, '').lstrip('/'))
parser.feed(file.read())
parser.reset()
self.extensions = extensions
def create_po_template(self):
django = polib.pofile(str(Path(self.locale_paths[0]).joinpath('django.pot')), check_for_duplicates = True)
djangojs = polib.pofile(str(Path(self.locale_paths[0]).joinpath('djangojs.pot')), check_for_duplicates = True)
self.add_po_domain(django, 'django')
self.add_po_domain(djangojs, 'djangojs')
for entry in djangojs:
django.append(entry)
django.header = "\nGNOME Shell extensions repository\n"
django.header += "\n"
django.header += "DO NOT EDIT!\n"
django.header += "This file is auto generated with manage.py makemessages."
django.header += "\n"
django.metadata = {
'Project-Id-Version': '1.0',
'Report-Msgid-Bugs-To': 'ykonotopov@gnome.org',
'POT-Creation-Date': datetime.now(pytz.utc).strftime('%Y-%m-%d %H:%M%z'),
'MIME-Version': '1.0',
'Content-Type': 'text/plain; charset=utf-8',
'Content-Transfer-Encoding': '8bit',
}
django.sort()
django.save(Path(self.po_path).joinpath("extensions-web.pot"))
def add_po_domain(self, po, domain):
for entry in po:
entry.occurrences.append((self.metadata_domain_prefix + domain, 1))
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-19 14:52+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: sweettooth/auth/forms.py:39
msgid "Username"
msgstr ""
#: sweettooth/auth/forms.py:40
msgid "Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only."
msgstr ""
#: sweettooth/auth/forms.py:41
msgid "This value may contain only letters, numbers and @/./+/-/_ characters."
msgstr ""
#: sweettooth/auth/forms.py:43
msgid "Email"
msgstr ""
#: sweettooth/auth/forms.py:44
msgid "Password"
msgstr ""
#: sweettooth/auth/forms.py:45
msgid "Password confirmation"
msgstr ""
#: sweettooth/auth/forms.py:46
msgid "Enter the same password as above, for verification."
msgstr ""
#: sweettooth/auth/templates/registration/login.html:6
msgid "User Login"
msgstr ""
#: sweettooth/auth/templates/registration/login.html:23
#: sweettooth/auth/templates/registration/login_popup_form.html:14
msgid "Forgot your password?"
msgstr ""
#: sweettooth/auth/templates/registration/login.html:32
#: sweettooth/auth/templates/registration/login_popup_form.html:11
#: sweettooth/templates/base.html:72
msgid "Log in"
msgstr ""
#: sweettooth/auth/templates/registration/login.html:37
#: sweettooth/auth/templates/registration/login_popup_form.html:17
msgid "Don't have an account?"
msgstr ""
#: sweettooth/auth/templates/registration/login.html:38
#: sweettooth/auth/templates/registration/login_popup_form.html:19
msgid "Register"
msgstr ""
#: sweettooth/auth/templates/registration/password_reset_confirm.html:8
#: sweettooth/auth/templates/registration/password_reset_form.html:7
msgid "Password reset"
msgstr ""
#: sweettooth/auth/templates/registration/password_reset_confirm.html:12
#: sweettooth/auth/templates/registration/password_reset_form.html:12
msgid "Reset your password"
msgstr ""
#: sweettooth/auth/templates/registration/password_reset_confirm.html:15
msgid ""
"The token for the password reset is incorrect. Please check your link and "
"try again."
msgstr ""
#: sweettooth/auth/templates/registration/password_reset_form.html:8
msgid ""
"Forgotten your password? Enter your e-mail address below, and we’ll e-mail "
"instructions for setting a new one."
msgstr ""
#: sweettooth/context_processors.py:9
msgid "Extensions"
msgstr ""
#: sweettooth/context_processors.py:13
msgid "Add yours"
msgstr ""
#: sweettooth/context_processors.py:17
msgid "Installed extensions"
msgstr ""
#: sweettooth/context_processors.py:21
msgid "About"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:3
msgid "Your opinion"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:6
msgid "Leave a…"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:7
msgid "Comment"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:8
msgid "Rating"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:9
msgid "Bug report"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:18
#, python-format
msgid ""
"Unfortunately, to help prevent spam, we require that you <a href="
"\"%(login_url)s\">log in to GNOME Shell Extensions</a> in order to post a "
"comment or report an error. You understand, right?"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:23
msgid "User Reviews"
msgstr ""
#: sweettooth/extensions/templates/extensions/comments.html:25
msgid "Loading reviews…"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:25
msgid "Upgrade this extension"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:26
msgid "Configure this extension"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:27
msgid "Uninstall this extension"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:53
msgid "Extension Homepage"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:58
msgid "Download"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:61
msgid "Shell version…"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:65
msgid "Extension version…"
msgstr ""
#: sweettooth/extensions/templates/extensions/detail.html:74
msgid ""
"A reviewer will review the extension you submitted to make sure there's "
"nothing too dangerous. You'll be emailed the result of the review."
msgstr ""
#: sweettooth/extensions/templates/extensions/list.html:13
msgid "Search for extensions…"
msgstr ""
#: sweettooth/extensions/templates/extensions/local.html:3
#: sweettooth/extensions/templates/extensions/local.html:8
msgid "Installed Extensions"
msgstr ""
#: sweettooth/extensions/templates/extensions/local.html:5
msgid "Shell settings"
msgstr ""
#: sweettooth/ratings/admin.py:13
msgid "Content"
msgstr ""
#: sweettooth/ratings/admin.py:16
msgid "Metadata"
msgstr ""
#: sweettooth/ratings/templates/comments/form.html:8
#: sweettooth/ratings/templates/comments/preview.html:27
msgid "What do you think about this GNOME extension?"
msgstr ""
#: sweettooth/ratings/templates/comments/form.html:26
#: sweettooth/ratings/templates/comments/preview.html:45
msgid "Post"
msgstr ""
#: sweettooth/ratings/templates/comments/form.html:27
#: sweettooth/ratings/templates/comments/preview.html:46
msgid "Preview"
msgstr ""
#: sweettooth/ratings/templates/comments/preview.html:4
#: sweettooth/ratings/templates/comments/preview.html:15
msgid "Preview your comment"
msgstr ""
#: sweettooth/ratings/templates/comments/preview.html:13
msgid "Please correct the error below"
msgid_plural "Please correct the errors below"
msgstr[0] ""
msgstr[1] ""
#: sweettooth/ratings/templates/comments/preview.html:18
msgid "Post Comment"
msgstr ""
#: sweettooth/ratings/templates/comments/preview.html:21
msgid "Edit your comment"
msgstr ""
#: sweettooth/templates/base.html:16
msgid "Latest extensions in GNOME Shell Extensions"
msgstr ""
#: sweettooth/templates/base.html:17 sweettooth/templates/base.html:50
msgid "GNOME Shell Extensions"
msgstr ""
#: sweettooth/templates/usermenu.html:5
msgid "User Profile"
msgstr ""
#: sweettooth/templates/usermenu.html:6
msgid "User Settings"
msgstr ""
#: sweettooth/templates/usermenu.html:7
msgid "Log out"
msgstr ""
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-12 14:45+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: sweettooth/static/js/fsui.js:40
msgid "Name"
msgstr ""
#: sweettooth/static/js/fsui.js:41
msgid "Recent"
msgstr ""
#: sweettooth/static/js/fsui.js:42
msgid "Downloads"
msgstr ""
#: sweettooth/static/js/fsui.js:43
msgid "Popularity"
msgstr ""
#: sweettooth/static/js/fsui.js:73
msgid "Sort by"
msgstr ""
#: sweettooth/static/js/fsui.js:124
msgid "Compatible with"
msgstr ""
......@@ -48,6 +48,7 @@ INSTALLED_APPS = (
'sweettooth.extensions',
'sweettooth.auth',
'sweettooth.core',
'sweettooth.review',
'sweettooth.errorreports',
'sweettooth.templates',
......@@ -110,8 +111,6 @@ DATABASES = {
LANGUAGE_CODE = 'en-us'
LOCALE_PATHS = [os.path.join(BASE_DIR, 'sweettooth', 'locale')]
TIME_ZONE = 'UTC'
USE_I18N = True
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment