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 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
 
@@ -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
- const textWithPlaceholders = text.replace(/\{\{([^}]+)\}\}/g, (match) => {
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
- const textWithPlaceholders = text.replace(/\{\{([^}]+)\}\}/g, (match) => {
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(value, lang, sourceLang, apiKey, config.useFallbackLanguages ?? true);
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 ?? true
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 should use UPPERCASE_SNAKE_CASE (e.g., SAVE_CHANGES)";
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 should use UPPERCASE_SNAKE_CASE (e.g., SAVE_CHANGES)";
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
  },