poly-lexis 0.8.1 → 0.9.1

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
  }
@@ -1321,10 +1322,20 @@ import * as path5 from "path";
1321
1322
  // src/translations/utils/deepl-translate-provider.ts
1322
1323
  init_esm_shims();
1323
1324
  init_language_fallback();
1324
- function preserveVariables(text) {
1325
+ function preserveVariables(text, protectedTerms = []) {
1325
1326
  const variableMap = /* @__PURE__ */ new Map();
1326
1327
  let placeholderIndex = 0;
1327
- 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) => {
1328
1339
  const placeholder = `XXX_${placeholderIndex}_XXX`;
1329
1340
  variableMap.set(placeholder, match);
1330
1341
  placeholderIndex++;
@@ -1356,7 +1367,7 @@ var DeepLTranslateProvider = class {
1356
1367
  return this.isFreeApi ? "https://api-free.deepl.com/v2/translate" : "https://api.deepl.com/v2/translate";
1357
1368
  }
1358
1369
  async translate(options) {
1359
- const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
1370
+ const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true, protectedTerms = [] } = options;
1360
1371
  if (!apiKey) {
1361
1372
  throw new Error(
1362
1373
  "DeepL API key is required. Set DEEPL_API_KEY environment variable or provide apiKey in options."
@@ -1370,7 +1381,7 @@ var DeepLTranslateProvider = class {
1370
1381
  logLanguageFallback(sourceLangResult, "deepl");
1371
1382
  resolvedSourceLang = sourceLangResult.resolvedLanguage;
1372
1383
  }
1373
- const { textWithPlaceholders, variableMap } = preserveVariables(text);
1384
+ const { textWithPlaceholders, variableMap } = preserveVariables(text, protectedTerms);
1374
1385
  const body = {
1375
1386
  text: [textWithPlaceholders],
1376
1387
  target_lang: normalizeLanguageCode(targetLangResult.resolvedLanguage),
@@ -1420,10 +1431,20 @@ var DeepLTranslateProvider = class {
1420
1431
  // src/translations/utils/google-translate-provider.ts
1421
1432
  init_esm_shims();
1422
1433
  init_language_fallback();
1423
- function preserveVariables2(text) {
1434
+ function preserveVariables2(text, protectedTerms = []) {
1424
1435
  const variableMap = /* @__PURE__ */ new Map();
1425
1436
  let placeholderIndex = 0;
1426
- 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) => {
1427
1448
  const placeholder = `XXX_${placeholderIndex}_XXX`;
1428
1449
  variableMap.set(placeholder, match);
1429
1450
  placeholderIndex++;
@@ -1445,7 +1466,7 @@ function restoreVariables2(text, variableMap) {
1445
1466
  }
1446
1467
  var GoogleTranslateProvider = class {
1447
1468
  async translate(options) {
1448
- const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true } = options;
1469
+ const { text, sourceLang, targetLang, apiKey, useFallbackLanguages = true, protectedTerms = [] } = options;
1449
1470
  if (!apiKey) {
1450
1471
  throw new Error(
1451
1472
  "Google Translate API key is required. Set GOOGLE_TRANSLATE_API_KEY environment variable or provide apiKey in options."
@@ -1459,7 +1480,7 @@ var GoogleTranslateProvider = class {
1459
1480
  logLanguageFallback(sourceLangResult, "google");
1460
1481
  resolvedSourceLang = sourceLangResult.resolvedLanguage;
1461
1482
  }
1462
- const { textWithPlaceholders, variableMap } = preserveVariables2(text);
1483
+ const { textWithPlaceholders, variableMap } = preserveVariables2(text, protectedTerms);
1463
1484
  const url = `https://translation.googleapis.com/language/translate/v2?key=${apiKey}`;
1464
1485
  const sourceForGoogle = resolvedSourceLang?.includes("_") ? resolvedSourceLang.split("_")[0] : resolvedSourceLang;
1465
1486
  const targetForGoogle = targetLangResult.resolvedLanguage.includes("_") ? targetLangResult.resolvedLanguage.split("_")[0] : targetLangResult.resolvedLanguage;
@@ -1514,14 +1535,15 @@ function setTranslationProvider(provider) {
1514
1535
  function getTranslationProvider() {
1515
1536
  return customProvider || defaultProvider;
1516
1537
  }
1517
- async function translateText(text, targetLang, sourceLang = "en", apiKey, useFallbackLanguages = true) {
1538
+ async function translateText(text, targetLang, sourceLang = "en", apiKey, useFallbackLanguages = true, protectedTerms = []) {
1518
1539
  const provider = getTranslationProvider();
1519
1540
  return provider.translate({
1520
1541
  text,
1521
1542
  sourceLang,
1522
1543
  targetLang,
1523
1544
  apiKey,
1524
- useFallbackLanguages
1545
+ useFallbackLanguages,
1546
+ protectedTerms
1525
1547
  });
1526
1548
  }
1527
1549
 
@@ -1572,7 +1594,14 @@ async function addTranslationKey(projectRoot, options) {
1572
1594
  targetTranslations[namespace] = {};
1573
1595
  }
1574
1596
  if (!targetTranslations[namespace][key] || targetTranslations[namespace][key].trim() === "") {
1575
- 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
+ );
1576
1605
  targetTranslations[namespace][key] = translated;
1577
1606
  const sorted = sortKeys(targetTranslations[namespace]);
1578
1607
  writeTranslation(translationsPath, lang, namespace, sorted);
@@ -2003,7 +2032,8 @@ Processing language: ${language}`);
2003
2032
  language,
2004
2033
  config.sourceLanguage,
2005
2034
  apiKey,
2006
- config.useFallbackLanguages ?? true
2035
+ config.useFallbackLanguages,
2036
+ config.protectedTerms
2007
2037
  );
2008
2038
  console.log(` ${language.toUpperCase()}: "${translated}"`);
2009
2039
  if (!dryRun) {
@@ -2370,8 +2400,8 @@ if (command === "find-unused") {
2370
2400
  message: "Enter translation key (UPPERCASE_SNAKE_CASE):",
2371
2401
  validate: (value2) => {
2372
2402
  if (!value2.trim()) return "Key is required";
2373
- if (!/^[A-Z0-9_]+$/.test(value2)) {
2374
- 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)";
2375
2405
  }
2376
2406
  return true;
2377
2407
  },
@@ -2584,8 +2614,8 @@ if (command === "find-unused") {
2584
2614
  message: "Enter translation key (UPPERCASE_SNAKE_CASE):",
2585
2615
  validate: (value2) => {
2586
2616
  if (!value2.trim()) return "Key is required";
2587
- if (!/^[A-Z0-9_]+$/.test(value2)) {
2588
- 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)";
2589
2619
  }
2590
2620
  return true;
2591
2621
  },