Updating translations and adding a translation tracker

This commit is contained in:
2025-04-28 18:55:06 +02:00
parent 14aea2a475
commit 1ba9a66c8e
11 changed files with 1487 additions and 6 deletions

View File

@@ -32,6 +32,19 @@ services:
- "traefik.http.routers.gui.rule=PathPrefix(`/`)"
- "traefik.http.services.gui.loadbalancer.server.port=5173"
i18n:
build:
context: ./i18n
restart: always
volumes:
- ./i18n/app/src:/app/src
- ./gui/rpk-gui/public:/app/public
labels:
- "traefik.enable=true"
- "traefik.http.routers.i18n.entrypoints=web"
- "traefik.http.routers.i18n.rule=PathPrefix(`/locales/add`)"
- "traefik.http.services.i18n.loadbalancer.server.port=8100"
proxy:
image: traefik:latest
restart: always

View File

@@ -73,8 +73,8 @@
}
},
"error": {
"info": "You may have forgotten to add the {{action}} component to {{resource}} resource.",
"404": "Sorry, the page you visited does not exist.",
"info": "You may have forgotten to add the {{action}} component to {{resource}} resource.",
"resource404": "Are you sure you have created the {{resource}} resource.",
"backHome": "Back Home"
}
@@ -163,5 +163,90 @@
"error": "auto save failure",
"loading": "saving...",
"idle": "waiting for changes"
},
"undefined": {
"undefined": "No translation",
"titles": {
"list": "No translation"
}
},
"schemas": {
"individual": {
"type": "Individual",
"lastname": "Lastname",
"surnames": "Surname",
"day_of_birth": "Date of birth",
"firstname": "Firstname",
"place_of_birth": "Place of birth",
"middlename": "Middlename"
},
"corporation": {
"type": "Corporation",
"activity": "Activity",
"title": "Title",
"employees": "Employees"
},
"employee": {
"position": "Position",
"entity_id": "Identity"
},
"institution": {
"type": "Institution",
"title": "Title",
"activity": "Activity",
"employees": "Employees"
},
"entity": {
"entity_data": "Informations",
"address": "Address"
},
"provision_template": {
"name": "Name",
"title": "Title",
"body": "Body"
},
"contract_template": {
"name": "Name",
"title": "Title",
"provisions": "Provisions",
"parties": "Parties",
"variables": "Variables"
},
"party_template": {
"entity_id": "Party Template",
"representative_id": "Representative",
"part": "Part"
},
"provision_template_reference": {
"provision_template_id": "Provision Template"
},
"dictionary_entry": {
"key": "Variable",
"value": "Value"
},
"contract_draft": {
"name": "Name",
"title": "Title",
"parties": "Parties",
"provisions": "Provisions",
"variables": "Variables"
},
"draft_party": {
"entity_id": "Client",
"part": "Part",
"representative_id": "Representative"
},
"contract_provision_template_reference": {
"provision_template_id": "Provision Template",
"type": "Type"
},
"provision_genuine": {
"title": "Title",
"body": "Body",
"type": "Type"
},
"draft_provision": {
"provision": "Provision"
}
}
}

View File

@@ -73,8 +73,8 @@
}
},
"error": {
"info": "Il manque l'action {{action}} component à la ressource {{resource}} .",
"404": "Cette page n'existe pas.",
"info": "Il manque l'action {{action}} component à la ressource {{resource}} .",
"resource404": "Cette page n'existe pas.",
"backHome": "Retour à l'accueil"
}
@@ -163,5 +163,90 @@
"error": "Sauvegarde automatique ratée",
"loading": "Sauvegarde...",
"idle": "En attente de modification"
},
"undefined": {
"undefined": "No translation",
"titles": {
"list": "No translation"
}
},
"schemas": {
"individual": {
"type": "Particulier",
"middlename": "Autres prénoms",
"lastname": "Nom",
"firstname": "Prénom",
"day_of_birth": "Date de naissance",
"surnames": "Surnoms",
"place_of_birth": "Lieu de naissance"
},
"corporation": {
"type": "Entreprise",
"title": "Titre",
"activity": "Activité",
"employees": "Employés"
},
"employee": {
"entity_id": "Identité",
"position": "Poste"
},
"institution": {
"type": "Institution",
"title": "Titre",
"employees": "Employés",
"activity": "Activité"
},
"entity": {
"entity_data": "Informations",
"address": "Adresse"
},
"provision_template": {
"name": "Nom",
"body": "Corps",
"title": "Titre"
},
"contract_template": {
"name": "Nom",
"title": "Titre",
"parties": "Parties",
"provisions": "Clauses",
"variables": "Variables"
},
"party_template": {
"entity_id": "Entité",
"part": "Rôle",
"representative_id": "Représentant"
},
"provision_template_reference": {
"provision_template_id": "Template de clause"
},
"dictionary_entry": {
"key": "Variable",
"value": "Valeur"
},
"contract_draft": {
"name": "Nom",
"parties": "Parties",
"title": "Titre",
"provisions": "Clauses",
"variables": "Variables"
},
"draft_party": {
"part": "Rôle",
"representative_id": "Représentant",
"entity_id": "Entité"
},
"contract_provision_template_reference": {
"type": "Template",
"provision_template_id": "Template de clause"
},
"provision_genuine": {
"type": "Personalisée",
"title": "Titre",
"body": "Corps"
},
"draft_provision": {
"provision": "Clause"
}
}
}

View File

@@ -10,8 +10,9 @@ i18n
.init({
supportedLngs: ["EN", "FR"],
backend: {
loadPath: "/locales/{{lng}}/{{ns}}.json", // locale files path
loadPath: "/locales/{{lng}}/{{ns}}.json", // "http/locales/{{lng}}/{{ns}}.json"
},
//saveMissing: true,
ns: ["common"],
defaultNS: "common",
fallbackLng: ["EN", "FR"],

View File

@@ -1,5 +1,5 @@
import { JSONSchema7Definition } from "json-schema";
import { RJSFSchema } from '@rjsf/utils';
import i18n from '../../../i18n'
const API_URL = "/api/v1";
@@ -19,6 +19,10 @@ const getJsonschema = async (): Promise<RJSFSchema> => {
return rawSchema;
}
function convertCamelToSnake(str: string): string {
return str.replace(/([a-zA-Z])(?=[A-Z])/g,'$1_').toLowerCase()
}
function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
let resource;
@@ -45,6 +49,12 @@ function buildResource(rawSchemas: RJSFSchema, resourceName: string) {
} else if (is_array(prop) && is_reference(prop.items)) {
resolveReference(rawSchemas, resource, prop.items);
}
if (prop.hasOwnProperty("title")) {
const shortResourceName = convertCamelToSnake(resourceName.replace(/(-Input|Create|Update)$/g, ""));
prop.title = i18n.t(`schemas.${shortResourceName}.${convertCamelToSnake(prop_name)}`, prop.title);
console.log(`schemas.${shortResourceName}.${convertCamelToSnake(prop_name)}`);
}
}
return resource;
@@ -161,7 +171,6 @@ function path_exists(rawSchemas: RJSFSchema, resource: RJSFSchema, path: string)
return has_descendant(rawSchemas, resource, path);
}
return has_descendant(rawSchemas, resource, path.substring(0, pointFirstPosition))
&& path_exists(
rawSchemas,

11
i18n/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM node:lts-alpine
WORKDIR /app
COPY app/package*.json ./
RUN npm install
COPY app/tsconfig*.json ./
COPY app/src ./src
EXPOSE 8100
CMD [ "npm", "--watch", "start" ]

26
i18n/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,26 @@
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
\*.local
# Editor directories and files
.vscode/_
!.vscode/extensions.json
.idea
.DS_Store
_.suo
_.ntvs_
_.njsproj
_.sln
\*.sw?

1164
i18n/app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
i18n/app/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "i18n Helper",
"version": "0.1.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "ts-node src/index.ts",
"build": "tsc",
"serve": "node dist/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"devDependencies": {
"@types/express": "^5.0.1",
"@types/node": "^22.15.3",
"express": "^5.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
},
"dependencies": {
"body-parser": "^2.2.0"
}
}

49
i18n/app/src/index.ts Normal file
View File

@@ -0,0 +1,49 @@
import express, { Request, Response } from 'express';
import bodyParser from "body-parser";
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
const app = express();
app.use(bodyParser.json());
const port = process.env.PORT || 8100;
app.post('/locales/add/:lng/:ns', (req: Request, res: Response) => {
const keyPath = Object.keys(req.body)[0];
const basePath = "public/locales";
const lgPath = `${basePath}/${req.params.lng}`;
if (!existsSync(lgPath)) {
mkdirSync(lgPath);
}
const filePath = `${lgPath}/common.json`;
let missingTrans;
try {
missingTrans = JSON.parse(readFileSync(filePath, 'utf8'));
} catch(err) {
missingTrans = JSON.parse("{}");
}
let current = missingTrans
const splitPath = keyPath.split(".");
for (let i=0; i < splitPath.length; i++) {
const key = splitPath[i];
if (! current.hasOwnProperty(key)) {
if (i + 1 == splitPath.length) {
current[key] = "No translation";
} else {
current[key] = JSON.parse("{}");
}
}
current = current[key];
}
writeFileSync(filePath, JSON.stringify(missingTrans, null, 2))
res.send("OK");
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

13
i18n/app/tsconfig.json Normal file
View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
}