Skip to content

Commit a7bf51a

Browse files
committed
build: Add i18n machinery
This adds an initial version of internationalisation support. It doesn’t support Transifex (translations have to be committed manually) for now. See `docs/i18n.md` for a guide to what commands to use and what the files do. Signed-off-by: Philip Withnall <[email protected]> Fixes: #760
1 parent d03ea62 commit a7bf51a

File tree

7 files changed

+157
-3
lines changed

7 files changed

+157
-3
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ kolibri_explore_plugin/static/collections
3636
# pytest files
3737
.pytest_cache/
3838
pytestdebug.log
39+
40+
# Translations
41+
kolibri_explore_plugin/locale/**/LC_MESSAGES/*.csv
42+
kolibri_explore_plugin/locale/en/LC_MESSAGES/profiles/*

docs/i18n.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
Internationalisation of kolibri-explore-plugin
2+
===
3+
4+
Overview
5+
---
6+
7+
Internationalisation (i18n) of kolibri-explore-plugin works the same way as i18n
8+
of Kolibri, to the extent that the build rules are named the same. It’s done
9+
using tooling from kolibri-tools, and an overview of the system is given
10+
[in the Kolibri documentation](https://kolibri-dev.readthedocs.io/en/develop/i18n.html).
11+
12+
An overview of i18n in Django is [here](https://docs.djangoproject.com/en/1.11/topics/i18n/),
13+
which covers the backend of kolibri-explore-plugin.
14+
15+
At a high level, translatable strings are extracted from Python, JavaScript and
16+
Vue files, and are collated into several files:
17+
* `kolibri_explore_plugin/locale/en/LC_MESSAGES/django.po`
18+
* `kolibri_explore_plugin/locale/en/LC_MESSAGES/kolibri_explore_plugin-messages.csv`
19+
* `kolibri_explore_plugin/locale/en/LC_MESSAGES/welcome-screen-messages.csv`
20+
21+
This extraction process is done by running `yarn i18n-extract`.
22+
23+
They are in several files because they are extracted from the Django backend and
24+
the different node frontend modules separately.
25+
26+
These files are checked into git, even though they are generated mechanically
27+
from the source code, so that translators or external translation tools can pick
28+
them up at any time, without having to run kolibri-tools on the code.
29+
30+
Translations are done (currently manually), and the results committed to git as:
31+
* `kolibri_explore_plugin/locale/${locale}/LC_MESSAGES/django.po`
32+
* `kolibri_explore_plugin/locale/${locale}/LC_MESSAGES/kolibri_explore_plugin.app-messages.json`
33+
* `kolibri_explore_plugin/locale/${locale}/LC_MESSAGES/welcome-screen-messages.json`
34+
35+
CSV versions of the JSON files are also available for review.
36+
37+
The translated files for Django now need to be compiled to a machine readable
38+
form. This is done by `yarn i18n-download-translations`. (The naming is because,
39+
in Kolibri, this step downloads updated translations from a translation website
40+
before compiling them --- we do not currently do that in kolibri-explore-plugin.)
41+
42+
It compiles the Django messages to the following file:
43+
* `kolibri_explore_plugin/locale/${locale}/LC_MESSAGES/django.mo`
44+
45+
This is loaded by Django to provide backend translations.
46+
47+
It also generates the following two files, which contain information about the
48+
locale (such as its name and currency and date formats). They are for use with
49+
the node [`intl`](https://www.npmjs.com/package/intl) and
50+
[`vue-intl`](https://www.npmjs.com/package/vue-intl) packages:
51+
* `kolibri_explore_plugin/assets/src/intl-locale-data.js`
52+
* `kolibri_explore_plugin/assets/src/vue-intl-locale-data.js`
53+
54+
The frontend loads the installed source JSON files (such as
55+
`kolibri_explore_plugin/locale/${locale}/LC_MESSAGES/kolibri_explore_plugin.app-messages.json`)
56+
at runtime. It does this by looking for them using a well-known name in
57+
`LOCALE_PATHS`, then the `WebpackBundleHook.frontend_messages()` function
58+
injects the contents of the JSON files into the frontend JavaScript with a call
59+
to `kolibriCoreAppGlobal.registerLanguageAssets()` to register them with the
60+
translation functions.
61+
62+
Configuration
63+
---
64+
65+
Internationalisation configuration is in two places:
66+
* `kolibri_explore_plugin/locale/language_info.json`
67+
* `setup.cfg`
68+
69+
The JSON file contains the list of languages which we want kolibri-explore-plugin
70+
translated to. It’s in a format needed by kolibri-tools documented
71+
[here](https://kolibri-dev.readthedocs.io/en/develop/i18n.html#adding-a-newly-supported-language).
72+
73+
`setup.cfg` is needed to point kolibri-tools at the right directories for
74+
extracting and compiling translatable strings.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../docs/i18n.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[
2+
{
3+
"crowdin_code": "en",
4+
"intl_code": "en",
5+
"language_name": "English",
6+
"english_name": "English",
7+
"default_font": "NotoSans"
8+
},
9+
{
10+
"crowdin_code": "es-ES",
11+
"intl_code": "es-es",
12+
"language_name": "Español (España)",
13+
"english_name": "Spanish (Spain)",
14+
"default_font": "NotoSans"
15+
}
16+
]

package.json

+15-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
"name": "kolibri-explore-plugin",
33
"description": "Monorepo with kolibri's Discovery page and custom channel presentations.",
44
"private": true,
5+
"config": {
6+
"kolibri_tools_i18n_modules": "--plugins kolibri_explore_plugin --namespace ek-components --searchPath ./packages/ek-components --namespace template-ui --searchPath ./packages/template-ui --namespace welcome-screen --searchPath ./packages/welcome-screen"
7+
},
58
"scripts": {
6-
"build": "yarn build:version && yarn build:packages && yarn deploy:welcome && yarn build:plugin && yarn build:info",
9+
"build": "yarn build:version && yarn build:packages && yarn deploy:welcome && yarn build:plugin && yarn build:info && yarn build:i18n",
710
"build:plugin": "./scripts/set_override.py default && kolibri-tools build prod --plugins kolibri_explore_plugin --transpile",
811
"build:packages": "yarn build:libs",
912
"build:libs": "lerna run build --ignore template-ui",
1013
"build:apps": "lerna run build --scope template-ui",
1114
"build:info": "./scripts/build_info.sh > kolibri_explore_plugin/static/build-info.json",
1215
"build:version": "python -m setuptools_scm",
16+
"build:i18n": "yarn run i18n-extract && yarn run i18n-django-compilemessages",
1317
"build-dist": "./scripts/clean.sh && yarn clean && yarn build && python -m build",
1418
"deploy:apps": "./scripts/bundle_bundles.py",
1519
"clean": "kolibri-tools build clean --plugins kolibri_explore_plugin",
@@ -20,7 +24,13 @@
2024
"bump-version": "./scripts/bump_version.py",
2125
"deploy:welcome": "cp -rf packages/welcome-screen/dist kolibri_explore_plugin/welcomeScreen && rm -f kolibri_explore_plugin/welcomeScreen/js/*map",
2226
"release": "./scripts/check_can_relese.sh && twine upload -s dist/*",
23-
"test:python": "python -m pytest"
27+
"test:python": "python -m pytest",
28+
"makemessages": "kolibri-tools i18n-extract-messages $npm_package_config_kolibri_tools_i18n_modules",
29+
"i18n-extract-backend": "cd kolibri_explore_plugin && python -m kolibri manage makemessages --skip-update -l en --ignore 'node_modules/*' --ignore 'kolibri_explore_plugin/dist/*'",
30+
"i18n-extract-frontend": "yarn run makemessages",
31+
"i18n-extract": "yarn run i18n-extract-frontend && yarn run i18n-extract-backend",
32+
"i18n-django-compilemessages": "cd kolibri_explore_plugin && PYTHONPATH=\"..:$$PYTHONPATH\" python -m kolibri manage compilemessages --skip-update",
33+
"i18n-download-translations": "kolibri-tools i18n-code-gen --output-dir ./kolibri_explore_plugin/assets/src --lang-info ./kolibri_explore_plugin/locale/language_info.json && yarn run i18n-django-compilemessages && yarn exec kolibri-tools i18n-create-message-files -- $npm_package_config_kolibri_tools_i18n_modules"
2434
},
2535
"workspaces": [
2636
"kolibri_explore_plugin/assets",
@@ -44,8 +54,10 @@
4454
},
4555
"dependencies": {
4656
"@babel/preset-env": "^7.22.10",
57+
"intl": "^1.2.4",
4758
"kolibri-design-system": "https://github.com/learningequality/kolibri-design-system#269b294bf562dd7d0e3a616aaace97bf0d3433c3",
48-
"node-sass": "file:dependency-stub"
59+
"node-sass": "file:dependency-stub",
60+
"vue-intl": "3.1.0"
4961
},
5062
"browserslist": [
5163
"extends browserslist-config-kolibri"

setup.cfg

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# FIXME: This has to live in setup.cfg rather than pyproject.toml for now,
2+
# because kolibri-tools explicitly loads setup.cfg.
3+
# See https://github.com/learningequality/kolibri/issues/11146
4+
[kolibri:i18n]
5+
project = kolibri_explore_plugin
6+
locale_data_folder = kolibri_explore_plugin/locale
7+
# Glob to exclude node_modules and static folders
8+
ignore = **/node_modules/**,**/static/**

yarn.lock

+39
Original file line numberDiff line numberDiff line change
@@ -10046,6 +10046,35 @@ interpret@^3.1.1:
1004610046
resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4"
1004710047
integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==
1004810048

10049+
intl-format-cache@^2.0.5:
10050+
version "2.2.9"
10051+
resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-2.2.9.tgz#fb560de20c549cda20b569cf1ffb6dc62b5b93b4"
10052+
integrity sha512-Zv/u8wRpekckv0cLkwpVdABYST4hZNTDaX7reFetrYTJwxExR2VyTqQm+l0WmL0Qo8Mjb9Tf33qnfj0T7pjxdQ==
10053+
10054+
10055+
version "1.2.0"
10056+
resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-1.2.0.tgz#5906b7f953ab7470e0dc8549097b648b991892ff"
10057+
integrity sha512-2qnIxfkrrzKAXR9m+rUqWb2yzeRX78k1l7+MU4ji7i4TIUyAIOo3KGV4hPKElRNyD40AAoAQdw1XXs5umB/usA==
10058+
10059+
[email protected], intl-messageformat@^1.3.0:
10060+
version "1.3.0"
10061+
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-1.3.0.tgz#f7d926aded7a3ab19b2dc601efd54e99a4bd4eae"
10062+
integrity sha512-dtj6VJ0Ul/DFyAksbEtOzsslkfuEu7r/d/oi8XltZgDLH3aUzxNkcb6SbguhHTi1RyPGtdWsfx6Q7qJGUySpow==
10063+
dependencies:
10064+
intl-messageformat-parser "1.2.0"
10065+
10066+
intl-relativeformat@^1.3.0:
10067+
version "1.3.0"
10068+
resolved "https://registry.yarnpkg.com/intl-relativeformat/-/intl-relativeformat-1.3.0.tgz#893dc7076fccd380cf091a2300c380fa57ace45b"
10069+
integrity sha512-pDbaJb3lh9IFyeY/bLZucvwcQgiUY6iqJ+ltMLi7FxMxIwzjHZrlSnvQbH3D0gQj1e1N9vpngh1kXMdfEbhPpA==
10070+
dependencies:
10071+
intl-messageformat "1.3.0"
10072+
10073+
intl@^1.2.4:
10074+
version "1.2.5"
10075+
resolved "https://registry.yarnpkg.com/intl/-/intl-1.2.5.tgz#82244a2190c4e419f8371f5aa34daa3420e2abde"
10076+
integrity sha512-rK0KcPHeBFBcqsErKSpvZnrOmWOj+EmDkyJ57e90YWaQNqbcivcqmKDlHEeNprDWOsKzPsh1BfSpPQdDvclHVw==
10077+
1004910078
[email protected], invariant@^2.2.2:
1005010079
version "2.2.4"
1005110080
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -17095,6 +17124,16 @@ vue-hot-reload-api@^2.3.0:
1709517124
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
1709617125
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
1709717126

17127+
17128+
version "3.1.0"
17129+
resolved "https://registry.yarnpkg.com/vue-intl/-/vue-intl-3.1.0.tgz#707f1f7406595c9b4afc6049254b333093be37be"
17130+
integrity sha512-0v3S5gspuYnt6j1G+KLfPUsNnjRdbMOcYrWYoSd1gYk6rX8VuOyh7NLztPrSIJt+NLs/qzLOZXxI1LORukEiqA==
17131+
dependencies:
17132+
babel-runtime "^6.26.0"
17133+
intl-format-cache "^2.0.5"
17134+
intl-messageformat "^1.3.0"
17135+
intl-relativeformat "^1.3.0"
17136+
1709817137
vue-jest@^3.0.0:
1709917138
version "3.0.7"
1710017139
resolved "https://registry.yarnpkg.com/vue-jest/-/vue-jest-3.0.7.tgz#a6d29758a5cb4d750f5d1242212be39be4296a33"

0 commit comments

Comments
 (0)