🌐(i18n) create package i18n

We create a package i18n to manage the translations of the project.
It help us to extract the translations from the frontend to
be deployed to crowdin.
It also help us to format the translations from crowdin to
be used by the frontend apps.
This commit is contained in:
Anthony LC
2024-01-24 15:16:44 +01:00
committed by Anthony LC
parent 7add42f525
commit 3d0824e023
12 changed files with 1179 additions and 46 deletions

View File

@@ -101,7 +101,7 @@ jobs:
test-e2e:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -254,29 +254,59 @@ jobs:
- name: Run tests
run: ~/.local/bin/pytest -n 2
i18n-back:
i18n-crowdin:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Install gettext (required to make messages)
run: |
sudo apt-get update
sudo apt-get install -y gettext
- name: Install Python
uses: actions/setup-python@v3
with:
python-version: '3.10'
- name: Install development dependencies
working-directory: src/backend
run: pip install --user .[dev]
- name: Generate the translation base file
run: ~/.local/bin/django-admin makemessages --keep-pot --all
- name: Load sops secrets
uses: rouja/actions-sops@main
with:
secret-file: .github/workflows/secrets.enc.env
age-key: ${{ secrets.SOPS_PRIVATE }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18.x'
cache: 'yarn'
cache-dependency-path: src/frontend/yarn.lock
- name: Install dependencies
run: cd src/frontend/ && yarn install --frozen-lockfile
- name: Download sources from Crowdin to stay synchronized
run: |
docker run \
--rm \
-e CROWDIN_API_TOKEN=$CROWDIN_API_TOKEN \
-e CROWDIN_PROJECT_ID=$CROWDIN_PROJECT_ID \
-e CROWDIN_BASE_PATH=$CROWDIN_BASE_PATH \
-v "${{ github.workspace }}:/app" \
crowdin/cli:3.16.0 \
crowdin download sources -c /app/crowdin/config.yml
- name: Extract the frontend translation
run: make frontend-i18n-extract
- name: Upload files to Crowdin
run: |
docker run \

1
.gitignore vendored
View File

@@ -78,3 +78,4 @@ db.sqlite3
.idea/
.vscode/
*.iml
.devcontainer/

View File

@@ -53,7 +53,8 @@ MAIL_YARN = $(COMPOSE_RUN) -w /app/src/mail node yarn
TSCLIENT_YARN = $(COMPOSE_RUN) -w /app/src/tsclient node yarn
# -- Frontend
PATH_FRONT_DESK = ./src/frontend/apps/desk
PATH_FRONT = ./src/frontend
PATH_FRONT_DESK = $(PATH_FRONT)/apps/desk
# ==============================================================================
# RULES
@@ -84,7 +85,7 @@ bootstrap: \
build \
run \
migrate \
i18n-compile \
back-i18n-compile \
mails-install \
mails-build \
install-front-desk
@@ -227,18 +228,24 @@ crowdin-download: ## Download translated message from crowdin
@$(COMPOSE_RUN_CROWDIN) download -c crowdin/config.yml
.PHONY: crowdin-download
crowdin-download-sources: ## Download sources from crowdin
@$(COMPOSE_RUN_CROWDIN) download sources -c crowdin/config.yml
.PHONY: crowdin-download-sources
crowdin-upload: ## Upload source translations to crowdin
@$(COMPOSE_RUN_CROWDIN) upload sources -c crowdin/config.yml
.PHONY: crowdin-upload
i18n-compile: ## compile all translations
i18n-compile: \
back-i18n-compile
back-i18n-compile \
frontend-i18n-compile
.PHONY: i18n-compile
i18n-generate: ## create the .pot files and extract frontend messages
i18n-generate: \
back-i18n-generate
back-i18n-generate \
frontend-i18n-generate
.PHONY: i18n-generate
i18n-download-and-compile: ## download all translated messages and compile them to be used by all applications
@@ -301,3 +308,17 @@ install-front-desk: ## Install the frontend dependencies of app Desk
run-front-desk: ## Start app Desk
cd $(PATH_FRONT_DESK) && yarn dev
.PHONY: run-front-desk
frontend-i18n-extract: ## Extract the frontend translation inside a json to be used for crowdin
cd $(PATH_FRONT) && yarn i18n:extract
.PHONY: frontend-i18n-extract
frontend-i18n-generate: ## Generate the frontend json files used for crowdin
frontend-i18n-generate: \
crowdin-download-sources \
frontend-i18n-extract
.PHONY: frontend-i18n-generate
frontend-i18n-compile: ## Format the crowin json files used deploy to the apps
cd $(PATH_FRONT) && yarn i18n:deploy
.PHONY: frontend-i18n-compile

View File

@@ -14,10 +14,17 @@ preserve_hierarchy: true
#
# Files configuration
#
files: [
{
source : "/backend/locale/django.pot",
dest: "/backend.pot",
translation : "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po"
},
]
files:
[
{
source: "/backend/locale/django.pot",
dest: "/backend.pot",
translation: "/backend/locale/%locale_with_underscore%/LC_MESSAGES/django.po",
},
{
source: "/frontend/packages/i18n/locales/desk/translations-crowdin.json",
dest: "/desk.json",
translation: "/frontend/packages/i18n/locales/desk/%two_letters_code%/translations.json",
skip_untranslated_strings: true,
},
]

View File

@@ -1,27 +1,18 @@
{
"extends": [
"github>numerique-gouv/renovate-configuration"
],
"extends": ["github>numerique-gouv/renovate-configuration"],
"dependencyDashboard": true,
"packageRules": [
{
"enabled": false,
"groupName": "ignored python dependencies",
"matchManagers": [
"pep621"
],
"matchManagers": ["pep621"],
"matchPackageNames": []
},
{
"enabled": false,
"groupName": "ignored js dependencies",
"matchManagers": [
"npm"
],
"matchPackageNames": [
"node",
"node-fetch"
]
"matchManagers": ["npm"],
"matchPackageNames": ["node", "node-fetch", "i18next-parser"]
}
]
}

View File

@@ -11,13 +11,16 @@
"scripts": {
"APP_DESK": "yarn workspace app-desk",
"APP_E2E": "yarn workspace app-e2e",
"I18N": "yarn workspace packages-i18n",
"app:dev": "yarn APP_DESK run dev",
"app:start": "yarn APP_DESK run start",
"app:build": "yarn APP_DESK run build",
"app:test": "yarn APP_DESK run test",
"ci:build": "yarn APP_DESK run build:ci",
"e2e:test": "yarn APP_E2E run test",
"lint": "yarn APP_DESK run lint && yarn APP_E2E run lint"
"lint": "yarn APP_DESK run lint && yarn APP_E2E run lint",
"i18n:extract": "yarn I18N run extract-translation",
"i18n:deploy": "yarn I18N run format-deploy"
},
"resolutions": {
"@types/node": "20.11.16",

1
src/frontend/packages/i18n/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
locales

View File

@@ -0,0 +1,49 @@
import fs from "fs";
import path from "path";
import { hideBin } from "yargs/helpers";
import yargs from "yargs/yargs";
// Get our args
const argv = yargs(hideBin(process.argv)).argv;
const { app, output } = argv;
const folderPath = "./locales/" + app;
const namefile = "translations.json";
const jsonI18n = {};
// Fetch the files in the locales folder
fs.readdirSync(folderPath).map((language) => {
const languagePath = path.join(folderPath, path.sep, language);
// Crowdin output file in folder, we want to treat only these ones
if (!fs.lstatSync(languagePath).isDirectory()) {
return;
}
jsonI18n[language] = {
translation: {},
};
// Get the json file generated by crowdin
const pathTranslateFile = path.join(languagePath, path.sep, namefile);
if (!fs.existsSync(pathTranslateFile)) {
return;
}
const json = JSON.parse(fs.readFileSync(pathTranslateFile, "utf8"));
// Transform the json file to the format expected by i18next
const jsonKeyMessage = {};
Object.keys(json).forEach((key) => {
jsonKeyMessage[key] = json[key].message;
});
jsonI18n[language] = {
translation: jsonKeyMessage,
};
});
// Write the file to the output
fs.writeFileSync(output, JSON.stringify(jsonI18n), "utf8");
console.log(`${app} translations deployed!`);

View File

@@ -0,0 +1,9 @@
const config = {
customValueTemplate: {
message: '${key}',
description: '${description}',
},
keepRemoved: false,
};
export default config;

View File

@@ -0,0 +1,9 @@
const config = {
customValueTemplate: {
message: "${key}",
description: "${description}",
},
keepRemoved: true,
};
export default config;

View File

@@ -0,0 +1,15 @@
{
"name": "packages-i18n",
"version": "0.1.0",
"private": true,
"scripts": {
"extract-translation": "yarn extract-translation:desk",
"extract-translation:desk": "yarn i18next ../../apps/desk/**/*.{ts,tsx} -c ./i18next-parser.config.mjs -o ./locales/desk/translations-crowdin.json",
"format-deploy": "yarn format-deploy:desk",
"format-deploy:desk": "node ./format-deploy.mjs --app=desk --output=../../apps/desk/src/i18n/translations.json"
},
"dependencies": {
"i18next-parser": "8.8.0",
"yargs": "17.7.2"
}
}

File diff suppressed because it is too large Load Diff