Currently we have some license issues. We are working on it.

Commit cfc44ed5 authored by jaywink's avatar jaywink
Browse files

Merge branch 'translate' into 'master'

Initial translation process and activation setup

See merge request !571
parents e96c378c f4ddf216
Pipeline #6090 passed with stages
in 12 minutes and 26 seconds
Requirements
------------
pybabel (already installed)
pip install django-babel
Patch django-babel with (assuming you are using virtualenv):
```
patch -p1 ~/.virtualenvs/socialhome/lib/python3.9/site-packages/django_babel/extract < ~/socialhome/translate/django_babel.patch
```
TODO: create an issue for this
Workflow
--------
From project directory,
Create or update the pot file using:
```
PYTHONPATH=. pybabel extract -F babel.cfg -o socialhome/locale/django.pot .
```
Translations are hosted at https://hosted.weblate.org/projects/socialhome. During the initial setup, weblate will pull the django.pot file.
Then new translations can be started using weblate's UI. The pattern to configure for po files is socialhome/locale/*/LC_MESSAGES/django.po.
weblate can be configure to automatically compile mo files, but until we figure out how it works, use the following:
```
django-admin compilemessages
```
Saving and archiving from the weblate project will push the updated po files to socialhome's repository.
If changes to the translation template file (django.pot) are made, they should first be merged with all existing translations with (bash script example):
```
for f in locale/*/LC_MESSAGES/django.po; do
msgmerge -U $f locale/django.pot # NOTE: pybabel update could also be used
done
```
and be pushed to the repo and then forced synced from the weblate UI.
Hack
----
~/socialhome/translate/extract.py is a wrapper around pybabel's javascript extractor to deal with template literal placeholders and filters.
TODO: create an issue for this.
NOTE: pybabel is configured tp parse compiled js only. If changes are made to js or vue files, npm run dev should be run before updating the translation files.
[extractors]
extrajs = translate.extract:extract_extrajs
[django: socialhome/templates/**.*]
[django: socialhome/*/templates/**.*]
[python: socialhome/**.py]
[extrajs: socialhome/static/**.js]
......@@ -84,10 +84,10 @@ SILKY_INSTALLED = env.bool("SOCIALHOME_SILKY", False)
# ------------------------------------------------------------------------------
MIDDLEWARE = (
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.contrib.sites.middleware.CurrentSiteMiddleware",
......
......@@ -69,7 +69,7 @@ urlpatterns = [
url(r"^bookmarklet/", ContentCreateView.as_view(), name="bookmarklet"),
# JavaScript translations
path("jsi18n/", JavaScriptCatalog.as_view(packages=['socialhome']), name="javascript-catalog"),
path("jsi18n/", JavaScriptCatalog.as_view(packages=['socialhome'], domain="django") , name="javascript-catalog"),
# Django URLs in JS
url(r"^jsreverse/$", urls_js, name="js_reverse"),
......
......@@ -15,7 +15,7 @@ from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import get_language, ugettext_lazy as _
from enumfields import EnumIntegerField
from federation.entities.activitypub.enums import ActivityType
from federation.utils.text import process_text_links, find_tags
......@@ -248,7 +248,7 @@ class Content(models.Model):
@property
def humanized_timestamp(self):
"""Human readable timestamp ie '2 hours ago'."""
return arrow.get(self.modified).humanize()
return arrow.get(self.modified).humanize(locale=get_language())
@cached_property
def root(self):
......
......@@ -776,6 +776,14 @@
"jsdom-global": "^3.0.2",
"mocha": "^6.2.2",
"mochapack": "^1.1.13"
},
"dependencies": {
"jsdom-global": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-3.0.2.tgz",
"integrity": "sha1-a9KZwTsMRiay2iwDk81DhdYGrLk=",
"dev": true
}
}
},
"@vue/cli-plugin-vuex": {
......@@ -6310,7 +6318,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
......@@ -6331,12 +6340,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
......@@ -6351,17 +6362,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
......@@ -6478,7 +6492,8 @@
"inherits": {
"version": "2.0.4",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
......@@ -6490,6 +6505,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
......@@ -6504,6 +6520,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
......@@ -6511,12 +6528,14 @@
"minimist": {
"version": "1.2.5",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
......@@ -6535,6 +6554,7 @@
"version": "0.5.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "^1.2.5"
}
......@@ -6596,7 +6616,8 @@
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.8",
......@@ -6624,7 +6645,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
......@@ -6636,6 +6658,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
......@@ -6713,7 +6736,8 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
......@@ -6749,6 +6773,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
......@@ -6768,6 +6793,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
......@@ -6811,12 +6837,14 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
......
......@@ -20,10 +20,10 @@
<div class="mt-1 grid-item-bar-links">
<template v-if="content.user_is_author">
<a :href="updateUrl">
<i class="fa fa-pencil" title="Update" aria-label="Update" />
<i class="fa fa-pencil" :title="translations.update" :aria-label="translations.update" />
</a>
<a :href="deleteUrl">
<i class="fa fa-remove" title="Delete" aria-label="Delete" />
<i class="fa fa-remove" :title="translations.delete" :aria-label="translations.delete" />
</a>
</template>
</div>
......@@ -73,6 +73,12 @@ const StreamElement = {
updateUrl() {
return Urls["content:update"]({pk: this.content.id})
},
translations() {
return {
delete: gettext("Delete"),
update: gettext("Update"),
}
},
},
mounted() {
this.layoutAfterTwitterOEmbeds()
......
This diff is collapsed.
This diff is collapsed.
*** extract.py.orig 2021-05-21 17:38:03.719644894 +0000
--- extract.py 2021-05-11 16:49:10.059785399 +0000
***************
*** 1,5 ****
# -*- coding: utf-8 -*-
! from django.template.base import Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK
from django.utils.translation import trim_whitespace
from django.utils.encoding import smart_text
--- 1,5 ----
# -*- coding: utf-8 -*-
! from django.template.base import Lexer, TokenType
from django.utils.translation import trim_whitespace
from django.utils.encoding import smart_text
***************
*** 59,65 ****
for t in text_lexer.tokenize():
lineno += t.contents.count('\n')
if intrans:
! if t.token_type == TOKEN_BLOCK:
endbmatch = endblock_re.match(t.contents)
pluralmatch = plural_re.match(t.contents)
if endbmatch:
--- 59,65 ----
for t in text_lexer.tokenize():
lineno += t.contents.count('\n')
if intrans:
! if t.token_type == TokenType.BLOCK:
endbmatch = endblock_re.match(t.contents)
pluralmatch = plural_re.match(t.contents)
if endbmatch:
***************
*** 106,123 ****
else:
raise SyntaxError('Translation blocks must not include '
'other block tags: %s' % t.contents)
! elif t.token_type == TOKEN_VAR:
if inplural:
plural.append('%%(%s)s' % t.contents)
else:
singular.append('%%(%s)s' % t.contents)
! elif t.token_type == TOKEN_TEXT:
if inplural:
plural.append(t.contents)
else:
singular.append(t.contents)
else:
! if t.token_type == TOKEN_BLOCK:
imatch = inline_re.match(t.contents)
bmatch = block_re.match(t.contents)
cmatches = constant_re.findall(t.contents)
--- 106,123 ----
else:
raise SyntaxError('Translation blocks must not include '
'other block tags: %s' % t.contents)
! elif t.token_type == TokenType.VAR:
if inplural:
plural.append('%%(%s)s' % t.contents)
else:
singular.append('%%(%s)s' % t.contents)
! elif t.token_type == TokenType.TEXT:
if inplural:
plural.append(t.contents)
else:
singular.append(t.contents)
else:
! if t.token_type == TokenType.BLOCK:
imatch = inline_re.match(t.contents)
bmatch = block_re.match(t.contents)
cmatches = constant_re.findall(t.contents)
***************
*** 152,158 ****
for cmatch in cmatches:
stripped_cmatch = strip_quotes(cmatch)
yield lineno, None, smart_text(stripped_cmatch), []
! elif t.token_type == TOKEN_VAR:
parts = t.contents.split('|')
cmatch = constant_re.match(parts[0])
if cmatch:
--- 152,158 ----
for cmatch in cmatches:
stripped_cmatch = strip_quotes(cmatch)
yield lineno, None, smart_text(stripped_cmatch), []
! elif t.token_type == TokenType.VAR:
parts = t.contents.split('|')
cmatch = constant_re.match(parts[0])
if cmatch:
***************
*** 167,170 ****
p1 = p1.strip('()')
p1 = strip_quotes(p1)
yield lineno, None, smart_text(p1), []
-
--- 167,169 ----
import re
import functools
from io import BytesIO
from babel.messages.extract import extract_javascript
from babel.messages.extract import DEFAULT_KEYWORDS
del(DEFAULT_KEYWORDS['_'])
del(DEFAULT_KEYWORDS['N_'])
def extract_extrajs(fileobj, keywords, comment_tags, options):
"""Extract template literal placeholders and filters from Javascript files.
:param fileobj: the file-like the messages should be extracted from
:param keywords: a list of keywords (i.e. function names) that should be recognize as translation functions
:param comment_tags: a list of translator tags to search for and include in the results
:param options: a dictionary of additional options (optional)
:return: an iterator over ``(lineno, funcname, message, comments)``
:rtype: ``iterator``
"""
encoding = options.get('encoding', 'utf-8')
c = fileobj.read().decode(encoding=encoding)
filtpat = re.compile('t\._f\("gettext"\)', re.UNICODE)
c = filtpat.sub('gettext', c)
comppat = re.compile(r'(\$\{gettext\((.*?)\)\})', re.UNICODE)
c = comppat.sub(r'`+gettext(\g<2>)+`', c, re.UNICODE)
for i in extract_javascript(
BytesIO(c.encode(encoding=encoding)),
DEFAULT_KEYWORDS.keys(),
comment_tags,
options):
if i:
yield (i[0], i[1], i[2], i[3])
if __name__ == '__main__':
import sys
print(sys.argv[1])
with open(sys.argv[1], 'rb') as f:
for i in extract_extrajs(f, ['gettext'], [], {}):
print(i)
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