Skip to content

Commit c63fa90

Browse files
committed
fix: detect dynamically-used translation keys in check-translations and restore missing saved.* translations
1 parent 3f499c8 commit c63fa90

3 files changed

Lines changed: 43 additions & 0 deletions

File tree

packages/i18n/locales/de.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,9 @@
646646
"untitledList": "Unbenannte Liste",
647647
"deleteList": "Liste l\u00f6schen",
648648
"removeIcon": "Symbol l\u00f6schen",
649+
"favorites": "Favoriten",
650+
"wantToGo": "Wunschziele",
651+
"starredPlaces": "Markierte Orte",
649652
"saved": "Gespeichert",
650653
"savedIn": "In \u201e{list}\u201c gespeichert",
651654
"savedInTwo": "In \u201e{list1}\u201c und \u201e{list2}\u201c gespeichert",

packages/i18n/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,9 @@
646646
"untitledList": "Unnamed list",
647647
"deleteList": "Delete list",
648648
"removeIcon": "Remove icon",
649+
"favorites": "Favorites",
650+
"wantToGo": "Want to go",
651+
"starredPlaces": "Starred places",
649652
"saved": "Saved",
650653
"savedIn": "Saved in \"{list}\"",
651654
"savedInTwo": "Saved in \"{list1}\" and \"{list2}\"",

packages/i18n/scripts/check-translations.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const SRC_DIRS = [
3232
join(REPO_ROOT, "apps", "web", "src"),
3333
join(REPO_ROOT, "apps", "mobile", "app"),
3434
join(REPO_ROOT, "apps", "mobile", "src"),
35+
join(REPO_ROOT, "apps", "api", "src"),
3536
];
3637
const FIX_MISSING = process.argv.includes("--fix-missing");
3738

@@ -172,6 +173,8 @@ const sourceFiles = SRC_DIRS.filter((d) => {
172173

173174
const usedKeys = new Set<string>();
174175
const namespacesWithDynamicCalls = new Set<string>();
176+
const sliceNamespaces = new Set<string>();
177+
const dollarPrefixedNames = new Set<string>();
175178

176179
for (const file of sourceFiles) {
177180
const content = readFileSync(file, "utf-8");
@@ -195,15 +198,49 @@ for (const file of sourceFiles) {
195198
if (dynamicPattern.test(content)) {
196199
namespacesWithDynamicCalls.add(ns);
197200
}
201+
202+
// Detect $-prefix convention: varName(expr.slice(1)) or varName(expr.substring(1))
203+
const slicePattern = new RegExp(`\\b${varName}\\([^)]*\\.(?:slice|substring)\\(1\\)`, "g");
204+
if (slicePattern.test(content)) {
205+
sliceNamespaces.add(ns);
206+
}
198207
}
199208

200209
// react-i18next: const { t } = useTranslation() — uses flat dotted keys like t("ns.key")
201210
const i18nextPattern = /\{\s*t\s*\}\s*=\s*useTranslation\(\)/;
202211
if (i18nextPattern.test(content)) {
212+
// String literal calls: t("ns.key")
203213
const flatPattern = /\bt\(\s*["']([^"']+)["']/g;
204214
for (const match of content.matchAll(flatPattern)) {
205215
usedKeys.add(match[1]);
206216
}
217+
218+
// Template literals without expressions: t(`ns.key`)
219+
for (const match of content.matchAll(/\bt\(`([^`$]+)`\)/g)) {
220+
usedKeys.add(match[1]);
221+
}
222+
223+
// Template literals with expressions: t(`prefix.${expr}`)
224+
for (const match of content.matchAll(/\bt\(`(\w+)\.\$\{/g)) {
225+
namespacesWithDynamicCalls.add(match[1]);
226+
}
227+
228+
// $-prefix convention in template literals: t(`ns.${expr.slice(1)}`)
229+
for (const match of content.matchAll(/\bt\(`(\w+)\.\$\{[^}]*\.(?:slice|substring)\(1\)/g)) {
230+
sliceNamespaces.add(match[1]);
231+
}
232+
}
233+
234+
// Collect $-prefixed entity names (convention for translatable names)
235+
for (const match of content.matchAll(/(?:name|label)["']?\s*[:=]\s*["']\$([a-zA-Z]\w*)["']/g)) {
236+
dollarPrefixedNames.add(match[1]);
237+
}
238+
}
239+
240+
// Derive dynamic keys: $-prefixed entity names + namespaces using slice(1) convention
241+
for (const word of dollarPrefixedNames) {
242+
for (const ns of sliceNamespaces) {
243+
usedKeys.add(`${ns}.${word}`);
207244
}
208245
}
209246

0 commit comments

Comments
 (0)