All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 54s
- Changed file permissions from 644 to 755 for various files in the mobile-app and mobile-app_legacy_rn_expo directories, ensuring executable permissions where necessary. - Added a new SQL migration script to the backend to introduce missing columns for club billing and invoice settings in the clubs table, maintaining synchronization with the current Sequelize model.
125 lines
4.6 KiB
JavaScript
Executable File
125 lines
4.6 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Setzt bewusste Cognates/Produktnamen explizit in allen Locales (Wert = en-US oder de).
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const ROOT = path.resolve(__dirname, '..');
|
|
const LOCALES_DIR = path.join(ROOT, 'frontend', 'src', 'i18n', 'locales');
|
|
|
|
const LOCALES = [
|
|
'fr', 'es', 'it', 'pl', 'ja', 'zh', 'th', 'tl', 'fil',
|
|
'en-US', 'en-GB', 'en-AU',
|
|
];
|
|
|
|
// Keys, die in Nicht-DE-Locales explizit gesetzt werden (Wert aus en-US-Merge)
|
|
const COGNATE_KEYS = [
|
|
'common.filter', 'common.details', 'common.name', 'common.status',
|
|
'common.optional', 'common.in', 'common.ok',
|
|
'navigation.clickTtBrowser',
|
|
'members.rowTooltipSeparator', 'members.ttrQttr', 'members.status',
|
|
'members.clickTtRequestShort',
|
|
'diary.standardDurationShort', 'diary.min',
|
|
'trainingStats.name', 'trainingStats.ttr', 'trainingStats.qttr',
|
|
'tournaments.knockoutLabel', 'tournaments.genderMixed', 'tournaments.optional',
|
|
'tournaments.index', 'tournaments.diff',
|
|
'billing.iban',
|
|
'schedule.code', 'schedule.team', 'schedule.teams', 'schedule.vs',
|
|
'teamManagement.team', 'teamManagement.teams',
|
|
'courtDrawingTool.strokeTypeTopspin', 'courtDrawingTool.strokeTypeFlip',
|
|
'courtDrawingTool.strokeTypeBlock',
|
|
'matchReportApi.vs', 'courtDrawing.ok',
|
|
'pdfGenerator.diff', 'pdfGenerator.lineupQttr',
|
|
'pendingApprovals.email', 'permissions.email',
|
|
'logs.details', 'logs.logDetailsLabels.ip',
|
|
'dialogs.info.ok', 'dialogs.confirm.ok',
|
|
'memberTransferDialog.server', 'memberTransferDialog.endpoint', 'memberTransferDialog.format',
|
|
'myTischtennis.title', 'myTischtennisAccount.title', 'myTischtennisAccount.teams',
|
|
'memberNotes.tags', 'nuscoreAnalyzer.title',
|
|
'predefinedActivities.min',
|
|
];
|
|
|
|
const EXTRA = {
|
|
fr: { 'members.address': 'Adresse' },
|
|
es: { 'members.address': 'Dirección' },
|
|
it: { 'members.address': 'Indirizzo' },
|
|
pl: { 'members.address': 'Adres' },
|
|
ja: { 'members.address': '住所' },
|
|
zh: { 'members.address': '地址' },
|
|
th: { 'members.address': 'ที่อยู่' },
|
|
tl: { 'members.address': 'Address', 'languages.th': 'Thai (ไทย)' },
|
|
fil: { 'members.address': 'Address', 'languages.th': 'Thai (ไทย)' },
|
|
};
|
|
|
|
function flatten(obj, prefix = '', out = {}) {
|
|
for (const [key, value] of Object.entries(obj || {})) {
|
|
const nextKey = prefix ? `${prefix}.${key}` : key;
|
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
flatten(value, nextKey, out);
|
|
} else if (typeof value === 'string') {
|
|
out[nextKey] = value;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function deepMerge(base, override) {
|
|
if (!base || typeof base !== 'object' || Array.isArray(base)) return override ?? base;
|
|
const result = { ...base };
|
|
for (const [key, value] of Object.entries(override || {})) {
|
|
if (
|
|
value && typeof value === 'object' && !Array.isArray(value) &&
|
|
result[key] && typeof result[key] === 'object' && !Array.isArray(result[key])
|
|
) {
|
|
result[key] = deepMerge(result[key], value);
|
|
} else {
|
|
result[key] = value;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function setByPath(obj, dotPath, value) {
|
|
const parts = dotPath.split('.');
|
|
let cur = obj;
|
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
if (!cur[parts[i]] || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};
|
|
cur = cur[parts[i]];
|
|
}
|
|
cur[parts[parts.length - 1]] = value;
|
|
}
|
|
|
|
function buildOverrides(deFlat, targetFlat) {
|
|
const out = {};
|
|
for (const [key, value] of Object.entries(targetFlat)) {
|
|
if (value !== deFlat[key]) setByPath(out, key, value);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const de = JSON.parse(fs.readFileSync(path.join(LOCALES_DIR, 'de.json'), 'utf8'));
|
|
const enUs = JSON.parse(fs.readFileSync(path.join(LOCALES_DIR, 'en-US.json'), 'utf8'));
|
|
const deFlat = flatten(de);
|
|
const enMerged = flatten(deepMerge(JSON.parse(JSON.stringify(de)), enUs));
|
|
|
|
for (const locale of LOCALES) {
|
|
const localePath = path.join(LOCALES_DIR, `${locale}.json`);
|
|
if (!fs.existsSync(localePath)) continue;
|
|
const localeJson = JSON.parse(fs.readFileSync(localePath, 'utf8'));
|
|
const merged = flatten(deepMerge(JSON.parse(JSON.stringify(de)), localeJson));
|
|
|
|
for (const key of COGNATE_KEYS) {
|
|
const val = enMerged[key] ?? deFlat[key];
|
|
if (val !== undefined) merged[key] = val;
|
|
}
|
|
for (const [key, val] of Object.entries(EXTRA[locale] || {})) {
|
|
merged[key] = val;
|
|
}
|
|
|
|
const overrides = buildOverrides(deFlat, merged);
|
|
fs.writeFileSync(localePath, `${JSON.stringify(overrides, null, 2)}\n`, 'utf8');
|
|
const stillDe = Object.keys(deFlat).filter((k) => merged[k] === deFlat[k]).length;
|
|
console.log(`${locale}: overrides=${Object.keys(flatten(overrides)).length}, stillDe=${stillDe}`);
|
|
}
|