poly-lexis 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/dist/cli/translations.js +51 -19
- package/dist/cli/translations.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +49 -16
- package/dist/index.js.map +1 -1
- package/dist/scripts/verify-translations.js +2 -1
- package/dist/scripts/verify-translations.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -150,7 +150,8 @@ poly-lexis uses a `.translationsrc.json` file in your project root for configura
|
|
|
150
150
|
"languages": ["en", "es", "fr", "de"],
|
|
151
151
|
"sourceLanguage": "en",
|
|
152
152
|
"typesOutputPath": "src/types/i18nTypes.ts",
|
|
153
|
-
"provider": "deepl"
|
|
153
|
+
"provider": "deepl",
|
|
154
|
+
"protectedTerms": ["MyBrand", "ProductName"]
|
|
154
155
|
}
|
|
155
156
|
```
|
|
156
157
|
|
|
@@ -161,6 +162,19 @@ poly-lexis uses a `.translationsrc.json` file in your project root for configura
|
|
|
161
162
|
- `sourceLanguage` - Source language for translations (default: `"en"`)
|
|
162
163
|
- `typesOutputPath` - Path to output TypeScript types (default: `src/types/i18nTypes.ts`)
|
|
163
164
|
- `provider` - Translation provider to use: `"deepl"` or `"google"` (default: `"deepl"`)
|
|
165
|
+
- `protectedTerms` - Words or phrases that should never be translated (default: `[]`)
|
|
166
|
+
|
|
167
|
+
### Protected Terms
|
|
168
|
+
|
|
169
|
+
Use `protectedTerms` to prevent specific words or phrases from being translated — useful for brand names, product names, or any term that must remain unchanged across all languages.
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"protectedTerms": ["Vandelay Industries", "MyProduct"]
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
These terms are replaced with placeholders before the text is sent to the translation API and restored afterwards, so the translation service never sees them.
|
|
164
178
|
|
|
165
179
|
### Environment Variables
|
|
166
180
|
|
package/dist/cli/translations.js
CHANGED
|
@@ -710,7 +710,8 @@ var init_types = __esm({
|
|
|
710
710
|
provider: "deepl",
|
|
711
711
|
useFallbackLanguages: true,
|
|
712
712
|
searchPaths: ["src", "app", "pages", "components"],
|
|
713
|
-
searchExtensions: [".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"]
|
|
713
|
+
searchExtensions: [".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"],
|
|
714
|
+
protectedTerms: []
|
|
714
715
|
};
|
|
715
716
|
DEFAULT_LANGUAGES = ["en", "fr", "it", "pl", "es", "pt", "de", "nl", "sv", "hu", "cs", "ja"];
|
|
716
717
|
}
|
|
@@ -903,7 +904,7 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
903
904
|
if (!fs3.existsSync(outputDir)) {
|
|
904
905
|
fs3.mkdirSync(outputDir, { recursive: true });
|
|
905
906
|
}
|
|
906
|
-
const typeString = typeTemplate(allKeys, namespaces);
|
|
907
|
+
const typeString = typeTemplate(allKeys, namespaces, config.languages);
|
|
907
908
|
fs3.writeFileSync(outputFilePath, typeString, "utf8");
|
|
908
909
|
console.log(`Generated types with ${allKeys.length} keys and ${namespaces.length} namespaces`);
|
|
909
910
|
console.log(`Output: ${outputFilePath}`);
|
|
@@ -926,12 +927,14 @@ var init_generate_types = __esm({
|
|
|
926
927
|
init_utils();
|
|
927
928
|
init_init();
|
|
928
929
|
PLURAL_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
|
|
929
|
-
typeTemplate = (translationKeys, namespaceKeys) => `
|
|
930
|
+
typeTemplate = (translationKeys, namespaceKeys, languages) => `
|
|
930
931
|
export const translationKeys = [${translationKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
931
932
|
export const namespaceKeys = [${namespaceKeys.map((key) => `"${key}"`).join(", ")}] as const;
|
|
933
|
+
export const languages = [${languages.map((lang) => `"${lang}"`).join(", ")}] as const;
|
|
932
934
|
|
|
933
935
|
export type TranslationKey = typeof translationKeys[number];
|
|
934
936
|
export type TranslationNamespace = typeof namespaceKeys[number];
|
|
937
|
+
export type Language = typeof languages[number];
|
|
935
938
|
`;
|
|
936
939
|
}
|
|
937
940
|
});
|
|
@@ -1319,10 +1322,20 @@ import * as path5 from "path";
|
|
|
1319
1322
|
// src/translations/utils/deepl-translate-provider.ts
|
|
1320
1323
|
init_esm_shims();
|
|
1321
1324
|
init_language_fallback();
|
|
1322
|
-
function preserveVariables(text) {
|
|
1325
|
+
function preserveVariables(text, protectedTerms = []) {
|
|
1323
1326
|
const variableMap = /* @__PURE__ */ new Map();
|
|
1324
1327
|
let placeholderIndex = 0;
|
|
1325
|
-
|
|
1328
|
+
let result = text;
|
|
1329
|
+
for (const term of protectedTerms) {
|
|
1330
|
+
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1331
|
+
result = result.replace(new RegExp(escaped, "g"), () => {
|
|
1332
|
+
const placeholder = `XXX_${placeholderIndex}_XXX`;
|
|
1333
|
+
variableMap.set(placeholder, term);
|
|
1334
|
+
placeholderIndex++;
|
|
1335
|
+
return placeholder;
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
const textWithPlaceholders = result.replace(/\{\{([^}]+)\}\}/g, (match) => {
|
|
1326
1339
|
const placeholder = `XXX_${placeholderIndex}_XXX`;
|
|
1327
1340
|
variableMap.set(placeholder, match);
|
|
1328
1341
|
placeholderIndex++;
|
|
@@ -1354,7 +1367,7 @@ var DeepLTranslateProvider = class {
|
|
|
1354
1367
|
return this.isFreeApi ? "https://api-free.deepl.com/v2/translate" : "https://api.deepl.com/v2/translate";
|
|
1355
1368
|
}
|
|
1356
1369
|
async translate(options) {
|
|
1357
|
-
const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
|
|
1370
|
+
const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true, protectedTerms = [] } = options;
|
|
1358
1371
|
if (!apiKey) {
|
|
1359
1372
|
throw new Error(
|
|
1360
1373
|
"DeepL API key is required. Set DEEPL_API_KEY environment variable or provide apiKey in options."
|
|
@@ -1368,7 +1381,7 @@ var DeepLTranslateProvider = class {
|
|
|
1368
1381
|
logLanguageFallback(sourceLangResult, "deepl");
|
|
1369
1382
|
resolvedSourceLang = sourceLangResult.resolvedLanguage;
|
|
1370
1383
|
}
|
|
1371
|
-
const { textWithPlaceholders, variableMap } = preserveVariables(text);
|
|
1384
|
+
const { textWithPlaceholders, variableMap } = preserveVariables(text, protectedTerms);
|
|
1372
1385
|
const body = {
|
|
1373
1386
|
text: [textWithPlaceholders],
|
|
1374
1387
|
target_lang: normalizeLanguageCode(targetLangResult.resolvedLanguage),
|
|
@@ -1418,10 +1431,20 @@ var DeepLTranslateProvider = class {
|
|
|
1418
1431
|
// src/translations/utils/google-translate-provider.ts
|
|
1419
1432
|
init_esm_shims();
|
|
1420
1433
|
init_language_fallback();
|
|
1421
|
-
function preserveVariables2(text) {
|
|
1434
|
+
function preserveVariables2(text, protectedTerms = []) {
|
|
1422
1435
|
const variableMap = /* @__PURE__ */ new Map();
|
|
1423
1436
|
let placeholderIndex = 0;
|
|
1424
|
-
|
|
1437
|
+
let result = text;
|
|
1438
|
+
for (const term of protectedTerms) {
|
|
1439
|
+
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1440
|
+
result = result.replace(new RegExp(escaped, "g"), () => {
|
|
1441
|
+
const placeholder = `XXX_${placeholderIndex}_XXX`;
|
|
1442
|
+
variableMap.set(placeholder, term);
|
|
1443
|
+
placeholderIndex++;
|
|
1444
|
+
return placeholder;
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
const textWithPlaceholders = result.replace(/\{\{([^}]+)\}\}/g, (match) => {
|
|
1425
1448
|
const placeholder = `XXX_${placeholderIndex}_XXX`;
|
|
1426
1449
|
variableMap.set(placeholder, match);
|
|
1427
1450
|
placeholderIndex++;
|
|
@@ -1443,7 +1466,7 @@ function restoreVariables2(text, variableMap) {
|
|
|
1443
1466
|
}
|
|
1444
1467
|
var GoogleTranslateProvider = class {
|
|
1445
1468
|
async translate(options) {
|
|
1446
|
-
const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
|
|
1469
|
+
const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true, protectedTerms = [] } = options;
|
|
1447
1470
|
if (!apiKey) {
|
|
1448
1471
|
throw new Error(
|
|
1449
1472
|
"Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY environment variable or provide apiKey in options."
|
|
@@ -1457,7 +1480,7 @@ var GoogleTranslateProvider = class {
|
|
|
1457
1480
|
logLanguageFallback(sourceLangResult, "google");
|
|
1458
1481
|
resolvedSourceLang = sourceLangResult.resolvedLanguage;
|
|
1459
1482
|
}
|
|
1460
|
-
const { textWithPlaceholders, variableMap } = preserveVariables2(text);
|
|
1483
|
+
const { textWithPlaceholders, variableMap } = preserveVariables2(text, protectedTerms);
|
|
1461
1484
|
const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
|
|
1462
1485
|
const sourceForGoogle = resolvedSourceLang?.includes("_") ? resolvedSourceLang.split("_")[0] : resolvedSourceLang;
|
|
1463
1486
|
const targetForGoogle = targetLangResult.resolvedLanguage.includes("_") ? targetLangResult.resolvedLanguage.split("_")[0] : targetLangResult.resolvedLanguage;
|
|
@@ -1512,14 +1535,15 @@ function setTranslationProvider(provider) {
|
|
|
1512
1535
|
function getTranslationProvider() {
|
|
1513
1536
|
return customProvider || defaultProvider;
|
|
1514
1537
|
}
|
|
1515
|
-
async function translateText(text, targetLang, sourceLang = "en", apiKey, useFallbackLanguages = true) {
|
|
1538
|
+
async function translateText(text, targetLang, sourceLang = "en", apiKey, useFallbackLanguages = true, protectedTerms = []) {
|
|
1516
1539
|
const provider = getTranslationProvider();
|
|
1517
1540
|
return provider.translate({
|
|
1518
1541
|
text,
|
|
1519
1542
|
sourceLang,
|
|
1520
1543
|
targetLang,
|
|
1521
1544
|
apiKey,
|
|
1522
|
-
useFallbackLanguages
|
|
1545
|
+
useFallbackLanguages,
|
|
1546
|
+
protectedTerms
|
|
1523
1547
|
});
|
|
1524
1548
|
}
|
|
1525
1549
|
|
|
@@ -1570,7 +1594,14 @@ async function addTranslationKey(projectRoot, options) {
|
|
|
1570
1594
|
targetTranslations[namespace] = {};
|
|
1571
1595
|
}
|
|
1572
1596
|
if (!targetTranslations[namespace][key] || targetTranslations[namespace][key].trim() === "") {
|
|
1573
|
-
const translated = await translateText(
|
|
1597
|
+
const translated = await translateText(
|
|
1598
|
+
value,
|
|
1599
|
+
lang,
|
|
1600
|
+
sourceLang,
|
|
1601
|
+
apiKey,
|
|
1602
|
+
config.useFallbackLanguages,
|
|
1603
|
+
config.protectedTerms
|
|
1604
|
+
);
|
|
1574
1605
|
targetTranslations[namespace][key] = translated;
|
|
1575
1606
|
const sorted = sortKeys(targetTranslations[namespace]);
|
|
1576
1607
|
writeTranslation(translationsPath, lang, namespace, sorted);
|
|
@@ -2001,7 +2032,8 @@ Processing language: ${language}`);
|
|
|
2001
2032
|
language,
|
|
2002
2033
|
config.sourceLanguage,
|
|
2003
2034
|
apiKey,
|
|
2004
|
-
config.useFallbackLanguages
|
|
2035
|
+
config.useFallbackLanguages,
|
|
2036
|
+
config.protectedTerms
|
|
2005
2037
|
);
|
|
2006
2038
|
console.log(` ${language.toUpperCase()}: "${translated}"`);
|
|
2007
2039
|
if (!dryRun) {
|
|
@@ -2368,8 +2400,8 @@ if (command === "find-unused") {
|
|
|
2368
2400
|
message: "Enter translation key (UPPERCASE_SNAKE_CASE):",
|
|
2369
2401
|
validate: (value2) => {
|
|
2370
2402
|
if (!value2.trim()) return "Key is required";
|
|
2371
|
-
if (!/^[A-Z0-9_]+$/.test(value2)) {
|
|
2372
|
-
return "Key
|
|
2403
|
+
if (!/^[A-Z0-9_]+$/.test(value2.toUpperCase())) {
|
|
2404
|
+
return "Key must be SNAKE_CASE (e.g., SAVE_CHANGES)";
|
|
2373
2405
|
}
|
|
2374
2406
|
return true;
|
|
2375
2407
|
},
|
|
@@ -2582,8 +2614,8 @@ if (command === "find-unused") {
|
|
|
2582
2614
|
message: "Enter translation key (UPPERCASE_SNAKE_CASE):",
|
|
2583
2615
|
validate: (value2) => {
|
|
2584
2616
|
if (!value2.trim()) return "Key is required";
|
|
2585
|
-
if (!/^[A-Z0-9_]+$/.test(value2)) {
|
|
2586
|
-
return "Key
|
|
2617
|
+
if (!/^[A-Z0-9_]+$/.test(value2.toUpperCase())) {
|
|
2618
|
+
return "Key must be SNAKE_CASE (e.g., SAVE_CHANGES)";
|
|
2587
2619
|
}
|
|
2588
2620
|
return true;
|
|
2589
2621
|
},
|