poly-lexis 0.9.2 → 0.11.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 +33 -0
- package/dist/cli/translations.js +382 -108
- package/dist/cli/translations.js.map +1 -1
- package/dist/index.d.ts +128 -4
- package/dist/index.js +365 -93
- package/dist/index.js.map +1 -1
- package/dist/scripts/generate-schema.js +8 -0
- package/dist/scripts/generate-schema.js.map +1 -1
- package/dist/scripts/verify-translations.js +147 -37
- package/dist/scripts/verify-translations.js.map +1 -1
- package/dist/translations/core/translations-config.schema.json +8 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -431,6 +431,14 @@ var init_schema = __esm({
|
|
|
431
431
|
type: "string"
|
|
432
432
|
},
|
|
433
433
|
default: [".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"]
|
|
434
|
+
},
|
|
435
|
+
protectedTerms: {
|
|
436
|
+
type: "array",
|
|
437
|
+
description: "List of terms that should never be translated (e.g. brand names, technical terms)",
|
|
438
|
+
items: {
|
|
439
|
+
type: "string"
|
|
440
|
+
},
|
|
441
|
+
default: []
|
|
434
442
|
}
|
|
435
443
|
},
|
|
436
444
|
required: ["translationsPath", "languages", "sourceLanguage"],
|
|
@@ -623,9 +631,99 @@ var init_language_fallback = __esm({
|
|
|
623
631
|
}
|
|
624
632
|
});
|
|
625
633
|
|
|
626
|
-
// src/translations/utils/
|
|
634
|
+
// src/translations/utils/metadata.ts
|
|
635
|
+
import { createHash } from "crypto";
|
|
627
636
|
import * as fs from "fs";
|
|
628
637
|
import * as path from "path";
|
|
638
|
+
function emptyMetadata() {
|
|
639
|
+
return { version: CURRENT_VERSION, languages: {} };
|
|
640
|
+
}
|
|
641
|
+
function hashSourceValue(value) {
|
|
642
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
643
|
+
}
|
|
644
|
+
function getMetadataPath(translationsPath) {
|
|
645
|
+
return path.join(translationsPath, METADATA_FILE_NAME);
|
|
646
|
+
}
|
|
647
|
+
function metadataExists(translationsPath) {
|
|
648
|
+
return fs.existsSync(getMetadataPath(translationsPath));
|
|
649
|
+
}
|
|
650
|
+
function readMetadata(translationsPath) {
|
|
651
|
+
const metadataPath = getMetadataPath(translationsPath);
|
|
652
|
+
if (!fs.existsSync(metadataPath)) {
|
|
653
|
+
return emptyMetadata();
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
const parsed = JSON.parse(fs.readFileSync(metadataPath, "utf-8"));
|
|
657
|
+
return {
|
|
658
|
+
version: parsed.version ?? CURRENT_VERSION,
|
|
659
|
+
languages: parsed.languages ?? {}
|
|
660
|
+
};
|
|
661
|
+
} catch {
|
|
662
|
+
return emptyMetadata();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function writeMetadata(translationsPath, metadata) {
|
|
666
|
+
if (!fs.existsSync(translationsPath)) {
|
|
667
|
+
fs.mkdirSync(translationsPath, { recursive: true });
|
|
668
|
+
}
|
|
669
|
+
const metadataPath = getMetadataPath(translationsPath);
|
|
670
|
+
fs.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
671
|
+
`, "utf-8");
|
|
672
|
+
}
|
|
673
|
+
function setSourceHash(metadata, language, namespace, key, sourceValue) {
|
|
674
|
+
if (!metadata.languages[language]) {
|
|
675
|
+
metadata.languages[language] = {};
|
|
676
|
+
}
|
|
677
|
+
if (!metadata.languages[language][namespace]) {
|
|
678
|
+
metadata.languages[language][namespace] = {};
|
|
679
|
+
}
|
|
680
|
+
metadata.languages[language][namespace][key] = hashSourceValue(sourceValue);
|
|
681
|
+
return metadata;
|
|
682
|
+
}
|
|
683
|
+
function getSourceHash(metadata, language, namespace, key) {
|
|
684
|
+
return metadata.languages[language]?.[namespace]?.[key];
|
|
685
|
+
}
|
|
686
|
+
function pruneMetadata(metadata, sourceKeysByNamespace) {
|
|
687
|
+
let changed = false;
|
|
688
|
+
for (const language of Object.keys(metadata.languages)) {
|
|
689
|
+
const namespaces = metadata.languages[language];
|
|
690
|
+
for (const namespace of Object.keys(namespaces)) {
|
|
691
|
+
const sourceKeys = sourceKeysByNamespace[namespace];
|
|
692
|
+
if (!sourceKeys) {
|
|
693
|
+
delete namespaces[namespace];
|
|
694
|
+
changed = true;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
for (const key of Object.keys(namespaces[namespace])) {
|
|
698
|
+
if (!sourceKeys.has(key)) {
|
|
699
|
+
delete namespaces[namespace][key];
|
|
700
|
+
changed = true;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
if (Object.keys(namespaces[namespace]).length === 0) {
|
|
704
|
+
delete namespaces[namespace];
|
|
705
|
+
changed = true;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
if (Object.keys(namespaces).length === 0) {
|
|
709
|
+
delete metadata.languages[language];
|
|
710
|
+
changed = true;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return changed;
|
|
714
|
+
}
|
|
715
|
+
var METADATA_FILE_NAME, CURRENT_VERSION;
|
|
716
|
+
var init_metadata = __esm({
|
|
717
|
+
"src/translations/utils/metadata.ts"() {
|
|
718
|
+
"use strict";
|
|
719
|
+
METADATA_FILE_NAME = ".translations-meta.json";
|
|
720
|
+
CURRENT_VERSION = 1;
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// src/translations/utils/utils.ts
|
|
725
|
+
import * as fs2 from "fs";
|
|
726
|
+
import * as path2 from "path";
|
|
629
727
|
function flattenObject(obj, prefix = "") {
|
|
630
728
|
const result = {};
|
|
631
729
|
for (const [key, value] of Object.entries(obj)) {
|
|
@@ -658,16 +756,16 @@ function isNestedObject(obj) {
|
|
|
658
756
|
return Object.values(obj).some((value) => typeof value === "object" && value !== null);
|
|
659
757
|
}
|
|
660
758
|
function readTranslations(translationsPath, language) {
|
|
661
|
-
const langPath =
|
|
662
|
-
if (!
|
|
759
|
+
const langPath = path2.join(translationsPath, language);
|
|
760
|
+
if (!fs2.existsSync(langPath)) {
|
|
663
761
|
return {};
|
|
664
762
|
}
|
|
665
|
-
const files =
|
|
763
|
+
const files = fs2.readdirSync(langPath).filter((f) => f.endsWith(".json"));
|
|
666
764
|
const translations = {};
|
|
667
765
|
for (const file of files) {
|
|
668
|
-
const namespace =
|
|
669
|
-
const filePath =
|
|
670
|
-
const content =
|
|
766
|
+
const namespace = path2.basename(file, ".json");
|
|
767
|
+
const filePath = path2.join(langPath, file);
|
|
768
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
671
769
|
const parsed = JSON.parse(content);
|
|
672
770
|
if (isNestedObject(parsed)) {
|
|
673
771
|
translations[namespace] = flattenObject(parsed);
|
|
@@ -678,26 +776,26 @@ function readTranslations(translationsPath, language) {
|
|
|
678
776
|
return translations;
|
|
679
777
|
}
|
|
680
778
|
function writeTranslation(translationsPath, language, namespace, translations) {
|
|
681
|
-
const langPath =
|
|
682
|
-
if (!
|
|
683
|
-
|
|
779
|
+
const langPath = path2.join(translationsPath, language);
|
|
780
|
+
if (!fs2.existsSync(langPath)) {
|
|
781
|
+
fs2.mkdirSync(langPath, { recursive: true });
|
|
684
782
|
}
|
|
685
|
-
const filePath =
|
|
686
|
-
|
|
783
|
+
const filePath = path2.join(langPath, `${namespace}.json`);
|
|
784
|
+
fs2.writeFileSync(filePath, `${JSON.stringify(translations, null, 2)}
|
|
687
785
|
`, "utf-8");
|
|
688
786
|
}
|
|
689
787
|
function getAvailableLanguages(translationsPath) {
|
|
690
|
-
if (!
|
|
788
|
+
if (!fs2.existsSync(translationsPath)) {
|
|
691
789
|
return [];
|
|
692
790
|
}
|
|
693
|
-
return
|
|
791
|
+
return fs2.readdirSync(translationsPath, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
|
|
694
792
|
}
|
|
695
793
|
function getNamespaces(translationsPath, language) {
|
|
696
|
-
const langPath =
|
|
697
|
-
if (!
|
|
794
|
+
const langPath = path2.join(translationsPath, language);
|
|
795
|
+
if (!fs2.existsSync(langPath)) {
|
|
698
796
|
return [];
|
|
699
797
|
}
|
|
700
|
-
return
|
|
798
|
+
return fs2.readdirSync(langPath).filter((f) => f.endsWith(".json")).map((f) => path2.basename(f, ".json"));
|
|
701
799
|
}
|
|
702
800
|
function hasInterpolation(text) {
|
|
703
801
|
return /\{\{[^}]+\}\}/g.test(text);
|
|
@@ -724,13 +822,13 @@ function sortKeys(obj) {
|
|
|
724
822
|
return sorted;
|
|
725
823
|
}
|
|
726
824
|
function ensureTranslationsStructure(translationsPath, languages) {
|
|
727
|
-
if (!
|
|
728
|
-
|
|
825
|
+
if (!fs2.existsSync(translationsPath)) {
|
|
826
|
+
fs2.mkdirSync(translationsPath, { recursive: true });
|
|
729
827
|
}
|
|
730
828
|
for (const lang of languages) {
|
|
731
|
-
const langPath =
|
|
732
|
-
if (!
|
|
733
|
-
|
|
829
|
+
const langPath = path2.join(translationsPath, lang);
|
|
830
|
+
if (!fs2.existsSync(langPath)) {
|
|
831
|
+
fs2.mkdirSync(langPath, { recursive: true });
|
|
734
832
|
}
|
|
735
833
|
}
|
|
736
834
|
}
|
|
@@ -761,8 +859,8 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
|
|
|
761
859
|
const targetNamespaces = getNamespaces(translationsPath, language);
|
|
762
860
|
for (const namespace of targetNamespaces) {
|
|
763
861
|
if (!sourceNamespaces.includes(namespace)) {
|
|
764
|
-
const filePath =
|
|
765
|
-
|
|
862
|
+
const filePath = path2.join(translationsPath, language, `${namespace}.json`);
|
|
863
|
+
fs2.unlinkSync(filePath);
|
|
766
864
|
result.removedNamespaces.push({
|
|
767
865
|
language,
|
|
768
866
|
namespace,
|
|
@@ -771,9 +869,9 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
|
|
|
771
869
|
}
|
|
772
870
|
}
|
|
773
871
|
for (const namespace of sourceNamespaces) {
|
|
774
|
-
const filePath =
|
|
872
|
+
const filePath = path2.join(translationsPath, language, `${namespace}.json`);
|
|
775
873
|
const sourceFile = sourceTranslations[namespace] || {};
|
|
776
|
-
if (
|
|
874
|
+
if (fs2.existsSync(filePath)) {
|
|
777
875
|
const targetFile = targetTranslations[namespace] || {};
|
|
778
876
|
let hasOrphanedKeys = false;
|
|
779
877
|
const cleanedFile = {};
|
|
@@ -813,11 +911,22 @@ function syncTranslationStructure(translationsPath, languages, sourceLanguage) {
|
|
|
813
911
|
});
|
|
814
912
|
}
|
|
815
913
|
}
|
|
914
|
+
if (metadataExists(translationsPath)) {
|
|
915
|
+
const sourceKeysByNamespace = {};
|
|
916
|
+
for (const namespace of sourceNamespaces) {
|
|
917
|
+
sourceKeysByNamespace[namespace] = new Set(Object.keys(sourceTranslations[namespace] || {}));
|
|
918
|
+
}
|
|
919
|
+
const metadata = readMetadata(translationsPath);
|
|
920
|
+
if (pruneMetadata(metadata, sourceKeysByNamespace)) {
|
|
921
|
+
writeMetadata(translationsPath, metadata);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
816
924
|
return result;
|
|
817
925
|
}
|
|
818
926
|
var init_utils = __esm({
|
|
819
927
|
"src/translations/utils/utils.ts"() {
|
|
820
928
|
"use strict";
|
|
929
|
+
init_metadata();
|
|
821
930
|
}
|
|
822
931
|
});
|
|
823
932
|
|
|
@@ -848,13 +957,13 @@ __export(init_exports, {
|
|
|
848
957
|
initTranslations: () => initTranslations,
|
|
849
958
|
loadConfig: () => loadConfig
|
|
850
959
|
});
|
|
851
|
-
import * as
|
|
852
|
-
import * as
|
|
960
|
+
import * as fs3 from "fs";
|
|
961
|
+
import * as path3 from "path";
|
|
853
962
|
function detectExistingTranslations(projectRoot) {
|
|
854
963
|
const possiblePaths = ["public/static/locales", "public/locales", "src/locales", "locales", "i18n", "translations"];
|
|
855
964
|
for (const possiblePath of possiblePaths) {
|
|
856
|
-
const fullPath =
|
|
857
|
-
if (
|
|
965
|
+
const fullPath = path3.join(projectRoot, possiblePath);
|
|
966
|
+
if (fs3.existsSync(fullPath)) {
|
|
858
967
|
const languages = getAvailableLanguages(fullPath);
|
|
859
968
|
if (languages.length > 0) {
|
|
860
969
|
return { path: possiblePath, languages };
|
|
@@ -884,7 +993,7 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
884
993
|
languages: validLanguages.length > 0 ? validLanguages : finalConfig.languages
|
|
885
994
|
};
|
|
886
995
|
}
|
|
887
|
-
const translationsPath =
|
|
996
|
+
const translationsPath = path3.join(projectRoot, finalConfig.translationsPath);
|
|
888
997
|
const languages = finalConfig.languages.length > 0 ? finalConfig.languages : [...DEFAULT_LANGUAGES];
|
|
889
998
|
console.log(`Project root: ${projectRoot}`);
|
|
890
999
|
console.log(`Translations path: ${translationsPath}`);
|
|
@@ -896,9 +1005,9 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
896
1005
|
}
|
|
897
1006
|
ensureTranslationsStructure(translationsPath, languages);
|
|
898
1007
|
const sourceLanguage = finalConfig.sourceLanguage;
|
|
899
|
-
const sourcePath =
|
|
900
|
-
const commonPath =
|
|
901
|
-
if (!
|
|
1008
|
+
const sourcePath = path3.join(translationsPath, sourceLanguage);
|
|
1009
|
+
const commonPath = path3.join(sourcePath, "common.json");
|
|
1010
|
+
if (!fs3.existsSync(commonPath)) {
|
|
902
1011
|
const sampleTranslations = {
|
|
903
1012
|
LOADING: "Loading",
|
|
904
1013
|
SAVE: "Save",
|
|
@@ -907,7 +1016,7 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
907
1016
|
ERROR: "Error",
|
|
908
1017
|
SUCCESS: "Success"
|
|
909
1018
|
};
|
|
910
|
-
|
|
1019
|
+
fs3.writeFileSync(commonPath, `${JSON.stringify(sampleTranslations, null, 2)}
|
|
911
1020
|
`, "utf-8");
|
|
912
1021
|
console.log(`Created sample file: ${commonPath}`);
|
|
913
1022
|
}
|
|
@@ -921,8 +1030,8 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
921
1030
|
console.log(` ${lang}: ${langFiles.map((f) => f.namespace).join(", ")}`);
|
|
922
1031
|
}
|
|
923
1032
|
}
|
|
924
|
-
const configPath =
|
|
925
|
-
if (!
|
|
1033
|
+
const configPath = path3.join(projectRoot, ".translationsrc.json");
|
|
1034
|
+
if (!fs3.existsSync(configPath)) {
|
|
926
1035
|
const configContent = {
|
|
927
1036
|
$schema: "./node_modules/poly-lexis/dist/translations/core/translations-config.schema.json",
|
|
928
1037
|
translationsPath: finalConfig.translationsPath,
|
|
@@ -931,7 +1040,7 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
931
1040
|
typesOutputPath: finalConfig.typesOutputPath,
|
|
932
1041
|
provider: finalConfig.provider
|
|
933
1042
|
};
|
|
934
|
-
|
|
1043
|
+
fs3.writeFileSync(configPath, `${JSON.stringify(configContent, null, 2)}
|
|
935
1044
|
`, "utf-8");
|
|
936
1045
|
console.log(`Created config file: ${configPath}`);
|
|
937
1046
|
}
|
|
@@ -940,8 +1049,8 @@ function initTranslations(projectRoot, config = {}) {
|
|
|
940
1049
|
console.log("=====");
|
|
941
1050
|
}
|
|
942
1051
|
function loadConfig(projectRoot) {
|
|
943
|
-
const configPath =
|
|
944
|
-
if (!
|
|
1052
|
+
const configPath = path3.join(projectRoot, ".translationsrc.json");
|
|
1053
|
+
if (!fs3.existsSync(configPath)) {
|
|
945
1054
|
const existing = detectExistingTranslations(projectRoot);
|
|
946
1055
|
if (existing.path && existing.languages.length > 0) {
|
|
947
1056
|
console.log(`\u2139\uFE0F No config found, but detected translations at ${existing.path}`);
|
|
@@ -953,7 +1062,7 @@ function loadConfig(projectRoot) {
|
|
|
953
1062
|
}
|
|
954
1063
|
return DEFAULT_CONFIG;
|
|
955
1064
|
}
|
|
956
|
-
const configContent =
|
|
1065
|
+
const configContent = fs3.readFileSync(configPath, "utf-8");
|
|
957
1066
|
const config = JSON.parse(configContent);
|
|
958
1067
|
if (config.languages) {
|
|
959
1068
|
const validation = validateLanguages(config.languages);
|
|
@@ -974,7 +1083,7 @@ var init_init = __esm({
|
|
|
974
1083
|
});
|
|
975
1084
|
|
|
976
1085
|
// src/translations/cli/add-key.ts
|
|
977
|
-
import * as
|
|
1086
|
+
import * as path5 from "path";
|
|
978
1087
|
|
|
979
1088
|
// src/translations/utils/deepl-translate-provider.ts
|
|
980
1089
|
init_language_fallback();
|
|
@@ -1180,6 +1289,9 @@ var GoogleTranslateProvider = class {
|
|
|
1180
1289
|
}
|
|
1181
1290
|
};
|
|
1182
1291
|
|
|
1292
|
+
// src/translations/cli/add-key.ts
|
|
1293
|
+
init_metadata();
|
|
1294
|
+
|
|
1183
1295
|
// src/translations/utils/translator.ts
|
|
1184
1296
|
var defaultProvider = new GoogleTranslateProvider();
|
|
1185
1297
|
var customProvider = null;
|
|
@@ -1215,8 +1327,8 @@ init_utils();
|
|
|
1215
1327
|
init_utils();
|
|
1216
1328
|
init_init();
|
|
1217
1329
|
import { execSync } from "child_process";
|
|
1218
|
-
import * as
|
|
1219
|
-
import * as
|
|
1330
|
+
import * as fs4 from "fs";
|
|
1331
|
+
import * as path4 from "path";
|
|
1220
1332
|
var PLURAL_SUFFIXES = ["_zero", "_one", "_two", "_few", "_many", "_other"];
|
|
1221
1333
|
function extractPluralBaseKeys(keys) {
|
|
1222
1334
|
const keySet = new Set(keys);
|
|
@@ -1249,11 +1361,11 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
1249
1361
|
console.log("Generating i18n types");
|
|
1250
1362
|
console.log("=====");
|
|
1251
1363
|
const config = loadConfig(projectRoot);
|
|
1252
|
-
const translationsPath =
|
|
1364
|
+
const translationsPath = path4.join(projectRoot, config.translationsPath);
|
|
1253
1365
|
const sourceLanguage = config.sourceLanguage;
|
|
1254
|
-
const outputFilePath =
|
|
1255
|
-
const dirPath =
|
|
1256
|
-
if (!
|
|
1366
|
+
const outputFilePath = path4.join(projectRoot, config.typesOutputPath);
|
|
1367
|
+
const dirPath = path4.join(translationsPath, sourceLanguage);
|
|
1368
|
+
if (!fs4.existsSync(dirPath)) {
|
|
1257
1369
|
throw new Error(`Source language directory not found: ${dirPath}`);
|
|
1258
1370
|
}
|
|
1259
1371
|
const namespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
@@ -1268,12 +1380,12 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
1268
1380
|
}
|
|
1269
1381
|
const pluralBaseKeys = extractPluralBaseKeys(allKeys);
|
|
1270
1382
|
allKeys = allKeys.concat(pluralBaseKeys);
|
|
1271
|
-
const outputDir =
|
|
1272
|
-
if (!
|
|
1273
|
-
|
|
1383
|
+
const outputDir = path4.dirname(outputFilePath);
|
|
1384
|
+
if (!fs4.existsSync(outputDir)) {
|
|
1385
|
+
fs4.mkdirSync(outputDir, { recursive: true });
|
|
1274
1386
|
}
|
|
1275
1387
|
const typeString = typeTemplate(allKeys, namespaces, config.languages);
|
|
1276
|
-
|
|
1388
|
+
fs4.writeFileSync(outputFilePath, typeString, "utf8");
|
|
1277
1389
|
console.log(`Generated types with ${allKeys.length} keys and ${namespaces.length} namespaces`);
|
|
1278
1390
|
console.log(`Output: ${outputFilePath}`);
|
|
1279
1391
|
try {
|
|
@@ -1292,7 +1404,7 @@ function generateTranslationTypes(projectRoot = process.cwd()) {
|
|
|
1292
1404
|
init_init();
|
|
1293
1405
|
async function addTranslationKey(projectRoot, options) {
|
|
1294
1406
|
const config = loadConfig(projectRoot);
|
|
1295
|
-
const translationsPath =
|
|
1407
|
+
const translationsPath = path5.join(projectRoot, config.translationsPath);
|
|
1296
1408
|
const { namespace, key, value, autoTranslate = false, apiKey } = options;
|
|
1297
1409
|
const currentProvider = getTranslationProvider();
|
|
1298
1410
|
const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
|
|
@@ -1326,6 +1438,7 @@ async function addTranslationKey(projectRoot, options) {
|
|
|
1326
1438
|
const otherLanguages = config.languages.filter((lang) => lang !== sourceLang);
|
|
1327
1439
|
if (autoTranslate && apiKey) {
|
|
1328
1440
|
console.log("\nAuto-translating to other languages...");
|
|
1441
|
+
const metadata = readMetadata(translationsPath);
|
|
1329
1442
|
for (const lang of otherLanguages) {
|
|
1330
1443
|
try {
|
|
1331
1444
|
const targetTranslations = readTranslations(translationsPath, lang);
|
|
@@ -1344,6 +1457,7 @@ async function addTranslationKey(projectRoot, options) {
|
|
|
1344
1457
|
targetTranslations[namespace][key] = translated;
|
|
1345
1458
|
const sorted = sortKeys(targetTranslations[namespace]);
|
|
1346
1459
|
writeTranslation(translationsPath, lang, namespace, sorted);
|
|
1460
|
+
setSourceHash(metadata, lang, namespace, key, value);
|
|
1347
1461
|
console.log(` \u2713 ${lang}: "${translated}"`);
|
|
1348
1462
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1349
1463
|
} else {
|
|
@@ -1353,6 +1467,7 @@ async function addTranslationKey(projectRoot, options) {
|
|
|
1353
1467
|
console.error(` \u2717 ${lang}: Translation failed - ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
1354
1468
|
}
|
|
1355
1469
|
}
|
|
1470
|
+
writeMetadata(translationsPath, metadata);
|
|
1356
1471
|
} else {
|
|
1357
1472
|
console.log("\nAdding empty values to other languages...");
|
|
1358
1473
|
for (const lang of otherLanguages) {
|
|
@@ -1397,23 +1512,27 @@ async function addTranslationKeys(projectRoot, entries, autoTranslate = false, a
|
|
|
1397
1512
|
}
|
|
1398
1513
|
|
|
1399
1514
|
// src/translations/cli/auto-fill.ts
|
|
1400
|
-
import * as
|
|
1515
|
+
import * as path7 from "path";
|
|
1516
|
+
init_metadata();
|
|
1401
1517
|
init_utils();
|
|
1402
1518
|
init_init();
|
|
1403
1519
|
|
|
1404
1520
|
// src/translations/cli/validate.ts
|
|
1521
|
+
init_metadata();
|
|
1405
1522
|
init_utils();
|
|
1406
1523
|
init_init();
|
|
1407
|
-
import * as
|
|
1524
|
+
import * as path6 from "path";
|
|
1408
1525
|
function validateTranslations(projectRoot = process.cwd()) {
|
|
1409
1526
|
const config = loadConfig(projectRoot);
|
|
1410
|
-
const translationsPath =
|
|
1527
|
+
const translationsPath = path6.join(projectRoot, config.translationsPath);
|
|
1411
1528
|
const sourceLanguage = config.sourceLanguage;
|
|
1412
1529
|
const missing = [];
|
|
1413
1530
|
const empty = [];
|
|
1414
1531
|
const orphaned = [];
|
|
1532
|
+
const stale = [];
|
|
1415
1533
|
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1416
1534
|
const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
1535
|
+
const metadata = readMetadata(translationsPath);
|
|
1417
1536
|
const languages = config.languages.filter((lang) => lang !== sourceLanguage);
|
|
1418
1537
|
const syncResult = syncTranslationStructure(translationsPath, config.languages, sourceLanguage);
|
|
1419
1538
|
if (syncResult.createdFiles.length > 0) {
|
|
@@ -1450,6 +1569,17 @@ function validateTranslations(projectRoot = process.cwd()) {
|
|
|
1450
1569
|
language,
|
|
1451
1570
|
sourceValue
|
|
1452
1571
|
});
|
|
1572
|
+
} else if (typeof targetValue === "string") {
|
|
1573
|
+
const recordedHash = getSourceHash(metadata, language, namespace, key);
|
|
1574
|
+
if (recordedHash !== void 0 && recordedHash !== hashSourceValue(sourceValue)) {
|
|
1575
|
+
stale.push({
|
|
1576
|
+
namespace,
|
|
1577
|
+
key,
|
|
1578
|
+
language,
|
|
1579
|
+
sourceValue,
|
|
1580
|
+
currentValue: targetValue
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1453
1583
|
}
|
|
1454
1584
|
}
|
|
1455
1585
|
for (const [key, targetValue] of Object.entries(targetKeys)) {
|
|
@@ -1465,7 +1595,7 @@ function validateTranslations(projectRoot = process.cwd()) {
|
|
|
1465
1595
|
}
|
|
1466
1596
|
}
|
|
1467
1597
|
const valid = !missing.length && !empty.length && !orphaned.length;
|
|
1468
|
-
if (valid) {
|
|
1598
|
+
if (valid && !stale.length) {
|
|
1469
1599
|
console.log("\u2713 All translations are valid!");
|
|
1470
1600
|
} else {
|
|
1471
1601
|
if (missing.length > 0) {
|
|
@@ -1498,18 +1628,73 @@ function validateTranslations(projectRoot = process.cwd()) {
|
|
|
1498
1628
|
console.log(` ... and ${orphaned.length - 10} more`);
|
|
1499
1629
|
}
|
|
1500
1630
|
}
|
|
1631
|
+
if (stale.length > 0) {
|
|
1632
|
+
console.log(
|
|
1633
|
+
`
|
|
1634
|
+
\u26A0 Found ${stale.length} potentially outdated translations (source value changed since translation):`
|
|
1635
|
+
);
|
|
1636
|
+
for (const item of stale.slice(0, 10)) {
|
|
1637
|
+
console.log(` ${item.language}/${item.namespace}.json -> ${item.key}`);
|
|
1638
|
+
}
|
|
1639
|
+
if (stale.length > 10) {
|
|
1640
|
+
console.log(` ... and ${stale.length - 10} more`);
|
|
1641
|
+
}
|
|
1642
|
+
console.log(" Run auto-fill with --retranslate-changed to update them.");
|
|
1643
|
+
}
|
|
1501
1644
|
}
|
|
1502
1645
|
console.log("=====");
|
|
1503
|
-
return { valid, missing, empty, orphaned };
|
|
1646
|
+
return { valid, missing, empty, orphaned, stale };
|
|
1504
1647
|
}
|
|
1505
|
-
function getMissingForLanguage(projectRoot, language) {
|
|
1648
|
+
function getMissingForLanguage(projectRoot, language, options = {}) {
|
|
1506
1649
|
const result = validateTranslations(projectRoot);
|
|
1507
1650
|
const items = [
|
|
1508
1651
|
...result.missing.filter((m) => m.language === language).map((m) => ({ ...m, type: "missing" })),
|
|
1509
1652
|
...result.empty.filter((e) => e.language === language).map((e) => ({ ...e, type: "empty" }))
|
|
1510
1653
|
];
|
|
1654
|
+
if (options.includeStale) {
|
|
1655
|
+
items.push(
|
|
1656
|
+
...result.stale.filter((s) => s.language === language).map((s) => ({
|
|
1657
|
+
namespace: s.namespace,
|
|
1658
|
+
key: s.key,
|
|
1659
|
+
language: s.language,
|
|
1660
|
+
sourceValue: s.sourceValue,
|
|
1661
|
+
type: "stale"
|
|
1662
|
+
}))
|
|
1663
|
+
);
|
|
1664
|
+
}
|
|
1511
1665
|
return items;
|
|
1512
1666
|
}
|
|
1667
|
+
function ensureBaselineMetadata(projectRoot = process.cwd()) {
|
|
1668
|
+
const config = loadConfig(projectRoot);
|
|
1669
|
+
const translationsPath = path6.join(projectRoot, config.translationsPath);
|
|
1670
|
+
const sourceLanguage = config.sourceLanguage;
|
|
1671
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1672
|
+
const sourceNamespaces = getNamespaces(translationsPath, sourceLanguage);
|
|
1673
|
+
const languages = config.languages.filter((lang) => lang !== sourceLanguage);
|
|
1674
|
+
const existedBefore = metadataExists(translationsPath);
|
|
1675
|
+
const metadata = readMetadata(translationsPath);
|
|
1676
|
+
let recorded = 0;
|
|
1677
|
+
for (const language of languages) {
|
|
1678
|
+
const targetTranslations = readTranslations(translationsPath, language);
|
|
1679
|
+
for (const namespace of sourceNamespaces) {
|
|
1680
|
+
const sourceKeys = sourceTranslations[namespace] || {};
|
|
1681
|
+
const targetKeys = targetTranslations[namespace] || {};
|
|
1682
|
+
for (const [key, sourceValue] of Object.entries(sourceKeys)) {
|
|
1683
|
+
const targetValue = targetKeys[key];
|
|
1684
|
+
const alreadyTranslated = typeof targetValue === "string" && targetValue.trim() !== "";
|
|
1685
|
+
const alreadyTracked = getSourceHash(metadata, language, namespace, key) !== void 0;
|
|
1686
|
+
if (alreadyTranslated && !alreadyTracked) {
|
|
1687
|
+
setSourceHash(metadata, language, namespace, key, sourceValue);
|
|
1688
|
+
recorded++;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (recorded > 0) {
|
|
1694
|
+
writeMetadata(translationsPath, metadata);
|
|
1695
|
+
}
|
|
1696
|
+
return { recorded, created: !existedBefore && recorded > 0 };
|
|
1697
|
+
}
|
|
1513
1698
|
|
|
1514
1699
|
// src/translations/cli/auto-fill.ts
|
|
1515
1700
|
async function processConcurrently(items, concurrency, processor) {
|
|
@@ -1532,10 +1717,31 @@ async function processConcurrently(items, concurrency, processor) {
|
|
|
1532
1717
|
await Promise.all(executing);
|
|
1533
1718
|
return results;
|
|
1534
1719
|
}
|
|
1720
|
+
function getFillItemsForLanguage(projectRoot, translationsPath, sourceLanguage, language, options) {
|
|
1721
|
+
if (options.force) {
|
|
1722
|
+
const sourceTranslations = readTranslations(translationsPath, sourceLanguage);
|
|
1723
|
+
const items = [];
|
|
1724
|
+
for (const [namespace, keys] of Object.entries(sourceTranslations)) {
|
|
1725
|
+
for (const [key, sourceValue] of Object.entries(keys)) {
|
|
1726
|
+
items.push({ namespace, key, language, sourceValue, type: "forced" });
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return items;
|
|
1730
|
+
}
|
|
1731
|
+
return getMissingForLanguage(projectRoot, language, { includeStale: options.retranslateChanged });
|
|
1732
|
+
}
|
|
1535
1733
|
async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
|
|
1536
1734
|
const config = loadConfig(projectRoot);
|
|
1537
|
-
const translationsPath =
|
|
1538
|
-
const {
|
|
1735
|
+
const translationsPath = path7.join(projectRoot, config.translationsPath);
|
|
1736
|
+
const {
|
|
1737
|
+
apiKey,
|
|
1738
|
+
limit = Infinity,
|
|
1739
|
+
delayMs = 50,
|
|
1740
|
+
dryRun = false,
|
|
1741
|
+
concurrency = 5,
|
|
1742
|
+
retranslateChanged = false,
|
|
1743
|
+
force = false
|
|
1744
|
+
} = options;
|
|
1539
1745
|
const currentProvider = getTranslationProvider();
|
|
1540
1746
|
const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
|
|
1541
1747
|
if (isDefaultGoogleProvider) {
|
|
@@ -1558,14 +1764,17 @@ async function autoFillTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
1558
1764
|
console.log(`Created ${syncResult.createdFiles.length} namespace files
|
|
1559
1765
|
`);
|
|
1560
1766
|
}
|
|
1767
|
+
const mode = force ? "all keys (--force)" : retranslateChanged ? "missing, empty & changed" : "missing & empty";
|
|
1561
1768
|
console.log("=====");
|
|
1562
1769
|
console.log("Auto-filling translations");
|
|
1563
1770
|
console.log("=====");
|
|
1564
1771
|
console.log(`Languages: ${languagesToProcess.join(", ")}`);
|
|
1772
|
+
console.log(`Mode: ${mode}`);
|
|
1565
1773
|
console.log(`Limit: ${limit === Infinity ? "unlimited" : limit}`);
|
|
1566
1774
|
console.log(`Concurrency: ${concurrency}`);
|
|
1567
1775
|
console.log(`Dry run: ${dryRun}`);
|
|
1568
1776
|
console.log("=====");
|
|
1777
|
+
const metadata = readMetadata(translationsPath);
|
|
1569
1778
|
let totalProcessed = 0;
|
|
1570
1779
|
let totalTranslated = 0;
|
|
1571
1780
|
for (const language of languagesToProcess) {
|
|
@@ -1576,9 +1785,12 @@ Reached limit of ${limit} translations`);
|
|
|
1576
1785
|
}
|
|
1577
1786
|
console.log(`
|
|
1578
1787
|
Processing language: ${language}`);
|
|
1579
|
-
const missing =
|
|
1788
|
+
const missing = getFillItemsForLanguage(projectRoot, translationsPath, config.sourceLanguage, language, {
|
|
1789
|
+
retranslateChanged,
|
|
1790
|
+
force
|
|
1791
|
+
});
|
|
1580
1792
|
if (!missing.length) {
|
|
1581
|
-
console.log("
|
|
1793
|
+
console.log(" Nothing to translate");
|
|
1582
1794
|
continue;
|
|
1583
1795
|
}
|
|
1584
1796
|
console.log(` Found ${missing.length} translations to fill`);
|
|
@@ -1607,6 +1819,7 @@ Processing language: ${language}`);
|
|
|
1607
1819
|
translations[item.namespace][item.key] = translated;
|
|
1608
1820
|
const sorted = sortKeys(translations[item.namespace]);
|
|
1609
1821
|
writeTranslation(translationsPath, language, item.namespace, sorted);
|
|
1822
|
+
setSourceHash(metadata, language, item.namespace, item.key, item.sourceValue);
|
|
1610
1823
|
console.log(" \u2713 Saved");
|
|
1611
1824
|
} else {
|
|
1612
1825
|
console.log(" \u2713 Dry run - not saved");
|
|
@@ -1622,6 +1835,9 @@ Processing language: ${language}`);
|
|
|
1622
1835
|
});
|
|
1623
1836
|
totalProcessed += itemsToProcess.length;
|
|
1624
1837
|
totalTranslated += results.filter((r) => r.success).length;
|
|
1838
|
+
if (!dryRun) {
|
|
1839
|
+
writeMetadata(translationsPath, metadata);
|
|
1840
|
+
}
|
|
1625
1841
|
}
|
|
1626
1842
|
console.log("\n=====");
|
|
1627
1843
|
console.log(`Total processed: ${totalProcessed}`);
|
|
@@ -1633,7 +1849,7 @@ Processing language: ${language}`);
|
|
|
1633
1849
|
}
|
|
1634
1850
|
async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
1635
1851
|
const config = loadConfig(projectRoot);
|
|
1636
|
-
const translationsPath =
|
|
1852
|
+
const translationsPath = path7.join(projectRoot, config.translationsPath);
|
|
1637
1853
|
const currentProvider = getTranslationProvider();
|
|
1638
1854
|
const isDefaultGoogleProvider = currentProvider.constructor.name === "GoogleTranslateProvider";
|
|
1639
1855
|
if (isDefaultGoogleProvider) {
|
|
@@ -1649,6 +1865,7 @@ async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
|
1649
1865
|
const targetTranslations = readTranslations(translationsPath, language);
|
|
1650
1866
|
const sourceKeys = sourceTranslations[namespace] || {};
|
|
1651
1867
|
const targetKeys = targetTranslations[namespace] || {};
|
|
1868
|
+
const metadata = readMetadata(translationsPath);
|
|
1652
1869
|
let count = 0;
|
|
1653
1870
|
for (const [key, sourceValue] of Object.entries(sourceKeys)) {
|
|
1654
1871
|
const targetValue = targetKeys[key];
|
|
@@ -1665,12 +1882,14 @@ async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
|
1665
1882
|
config.protectedTerms ?? []
|
|
1666
1883
|
);
|
|
1667
1884
|
targetKeys[key] = translated;
|
|
1885
|
+
setSourceHash(metadata, language, namespace, key, sourceValue);
|
|
1668
1886
|
count++;
|
|
1669
1887
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1670
1888
|
}
|
|
1671
1889
|
if (count > 0) {
|
|
1672
1890
|
const sorted = sortKeys(targetKeys);
|
|
1673
1891
|
writeTranslation(translationsPath, language, namespace, sorted);
|
|
1892
|
+
writeMetadata(translationsPath, metadata);
|
|
1674
1893
|
console.log(`\u2713 Filled ${count} translations`);
|
|
1675
1894
|
} else {
|
|
1676
1895
|
console.log("No translations to fill");
|
|
@@ -1681,8 +1900,8 @@ async function fillNamespace(projectRoot, language, namespace, apiKey) {
|
|
|
1681
1900
|
init_utils();
|
|
1682
1901
|
init_init();
|
|
1683
1902
|
import { execSync as execSync2, spawnSync } from "child_process";
|
|
1684
|
-
import * as
|
|
1685
|
-
import * as
|
|
1903
|
+
import * as fs5 from "fs";
|
|
1904
|
+
import * as path8 from "path";
|
|
1686
1905
|
function isRipgrepAvailable() {
|
|
1687
1906
|
try {
|
|
1688
1907
|
execSync2("rg --version", { stdio: "ignore" });
|
|
@@ -1743,15 +1962,15 @@ function findFiles(rootDir, searchPaths, extensions) {
|
|
|
1743
1962
|
]);
|
|
1744
1963
|
function scanDirectory(dir) {
|
|
1745
1964
|
try {
|
|
1746
|
-
const entries =
|
|
1965
|
+
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1747
1966
|
for (const entry of entries) {
|
|
1748
|
-
const fullPath =
|
|
1967
|
+
const fullPath = path8.join(dir, entry.name);
|
|
1749
1968
|
if (entry.isDirectory()) {
|
|
1750
1969
|
if (!excludeDirs.has(entry.name)) {
|
|
1751
1970
|
scanDirectory(fullPath);
|
|
1752
1971
|
}
|
|
1753
1972
|
} else if (entry.isFile()) {
|
|
1754
|
-
const ext =
|
|
1973
|
+
const ext = path8.extname(entry.name);
|
|
1755
1974
|
if (extensionSet.has(ext)) {
|
|
1756
1975
|
files.push(fullPath);
|
|
1757
1976
|
}
|
|
@@ -1761,8 +1980,8 @@ function findFiles(rootDir, searchPaths, extensions) {
|
|
|
1761
1980
|
}
|
|
1762
1981
|
}
|
|
1763
1982
|
for (const searchPath of searchPaths) {
|
|
1764
|
-
const fullSearchPath =
|
|
1765
|
-
if (
|
|
1983
|
+
const fullSearchPath = path8.join(rootDir, searchPath);
|
|
1984
|
+
if (fs5.existsSync(fullSearchPath)) {
|
|
1766
1985
|
scanDirectory(fullSearchPath);
|
|
1767
1986
|
}
|
|
1768
1987
|
}
|
|
@@ -1770,7 +1989,7 @@ function findFiles(rootDir, searchPaths, extensions) {
|
|
|
1770
1989
|
}
|
|
1771
1990
|
function checkKeyInFile(key, filePath) {
|
|
1772
1991
|
try {
|
|
1773
|
-
const content =
|
|
1992
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1774
1993
|
const exactPatterns = [new RegExp(`['"\`]${key}['"\`]`, "g"), new RegExp(`\\b${key}\\b`, "g")];
|
|
1775
1994
|
for (const pattern of exactPatterns) {
|
|
1776
1995
|
if (pattern.test(content)) {
|
|
@@ -1793,7 +2012,7 @@ function checkPartialMatches(key, files) {
|
|
|
1793
2012
|
const foundParts = /* @__PURE__ */ new Set();
|
|
1794
2013
|
for (const file of filesToCheck) {
|
|
1795
2014
|
try {
|
|
1796
|
-
const content =
|
|
2015
|
+
const content = fs5.readFileSync(file, "utf-8");
|
|
1797
2016
|
for (const part of significantParts) {
|
|
1798
2017
|
if (foundParts.has(part)) continue;
|
|
1799
2018
|
const partPattern = new RegExp(`\\b${part}\\b`, "gi");
|
|
@@ -1813,7 +2032,7 @@ function checkPartialMatches(key, files) {
|
|
|
1813
2032
|
}
|
|
1814
2033
|
function findUnusedKeys(projectRoot = process.cwd()) {
|
|
1815
2034
|
const config = loadConfig(projectRoot);
|
|
1816
|
-
const translationsPath =
|
|
2035
|
+
const translationsPath = path8.join(projectRoot, config.translationsPath);
|
|
1817
2036
|
const sourceLanguage = config.sourceLanguage;
|
|
1818
2037
|
const searchPaths = config.searchPaths || ["src", "app", "pages", "components"];
|
|
1819
2038
|
const searchExtensions = config.searchExtensions || [".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"];
|
|
@@ -1955,13 +2174,13 @@ init_init();
|
|
|
1955
2174
|
init_schema();
|
|
1956
2175
|
init_types();
|
|
1957
2176
|
init_init();
|
|
1958
|
-
import * as
|
|
1959
|
-
import * as
|
|
2177
|
+
import * as fs6 from "fs";
|
|
2178
|
+
import * as path9 from "path";
|
|
1960
2179
|
import { checkbox, confirm, input, select } from "@inquirer/prompts";
|
|
1961
2180
|
async function initTranslationsInteractive(projectRoot = process.cwd()) {
|
|
1962
2181
|
console.log("\n\u{1F30D} Translation System Setup\n");
|
|
1963
|
-
const configPath =
|
|
1964
|
-
const alreadyExists =
|
|
2182
|
+
const configPath = path9.join(projectRoot, ".translationsrc.json");
|
|
2183
|
+
const alreadyExists = fs6.existsSync(configPath);
|
|
1965
2184
|
if (alreadyExists) {
|
|
1966
2185
|
console.log("\u26A0\uFE0F Configuration file already exists at .translationsrc.json\n");
|
|
1967
2186
|
const shouldOverwrite = await confirm({
|
|
@@ -2129,16 +2348,26 @@ function getLanguageName(code) {
|
|
|
2129
2348
|
|
|
2130
2349
|
// src/translations/cli/manage.ts
|
|
2131
2350
|
init_utils();
|
|
2132
|
-
import * as
|
|
2133
|
-
import * as
|
|
2351
|
+
import * as fs7 from "fs";
|
|
2352
|
+
import * as path10 from "path";
|
|
2134
2353
|
init_init();
|
|
2135
2354
|
async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
2136
|
-
const {
|
|
2355
|
+
const {
|
|
2356
|
+
autoFill = false,
|
|
2357
|
+
apiKey,
|
|
2358
|
+
limit,
|
|
2359
|
+
concurrency = 5,
|
|
2360
|
+
language,
|
|
2361
|
+
skipTypes = false,
|
|
2362
|
+
dryRun = false,
|
|
2363
|
+
retranslateChanged = false,
|
|
2364
|
+
force = false
|
|
2365
|
+
} = options;
|
|
2137
2366
|
console.log("=====");
|
|
2138
2367
|
console.log("Translation Management");
|
|
2139
2368
|
console.log("=====");
|
|
2140
|
-
const configPath =
|
|
2141
|
-
const isInitialized =
|
|
2369
|
+
const configPath = path10.join(projectRoot, ".translationsrc.json");
|
|
2370
|
+
const isInitialized = fs7.existsSync(configPath);
|
|
2142
2371
|
if (!isInitialized) {
|
|
2143
2372
|
console.log("\u{1F4C1} No translation configuration found. Initializing...\n");
|
|
2144
2373
|
initTranslations(projectRoot);
|
|
@@ -2147,9 +2376,9 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2147
2376
|
console.log("\u2713 Translation structure initialized\n");
|
|
2148
2377
|
}
|
|
2149
2378
|
const config = loadConfig(projectRoot);
|
|
2150
|
-
const translationsPath =
|
|
2151
|
-
const sourceLangPath =
|
|
2152
|
-
if (!
|
|
2379
|
+
const translationsPath = path10.join(projectRoot, config.translationsPath);
|
|
2380
|
+
const sourceLangPath = path10.join(translationsPath, config.sourceLanguage);
|
|
2381
|
+
if (!fs7.existsSync(sourceLangPath)) {
|
|
2153
2382
|
console.log(`\u26A0\uFE0F Source language directory not found: ${sourceLangPath}`);
|
|
2154
2383
|
console.log("Please add translation files to the source language directory.\n");
|
|
2155
2384
|
return false;
|
|
@@ -2171,11 +2400,25 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2171
2400
|
if (syncResult.createdFiles.length === 0 && syncResult.cleanedKeys.length === 0 && syncResult.removedNamespaces.length === 0) {
|
|
2172
2401
|
console.log("\u2713 Translation structure is already synchronized\n");
|
|
2173
2402
|
}
|
|
2403
|
+
if (!dryRun) {
|
|
2404
|
+
const baseline = ensureBaselineMetadata(projectRoot);
|
|
2405
|
+
if (baseline.recorded > 0) {
|
|
2406
|
+
const verb = baseline.created ? "Created change-tracking baseline for" : "Added change tracking for";
|
|
2407
|
+
console.log(`\u{1F4CC} ${verb} ${baseline.recorded} existing translations
|
|
2408
|
+
`);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2174
2411
|
console.log("\u{1F50D} Validating translations...\n");
|
|
2175
2412
|
const validationResult = validateTranslations(projectRoot);
|
|
2176
|
-
|
|
2413
|
+
const hasWorkToFill = !validationResult.valid || force || retranslateChanged && validationResult.stale.length > 0;
|
|
2414
|
+
if (validationResult.valid && !validationResult.stale.length) {
|
|
2177
2415
|
console.log("\n\u2705 All translations are complete!\n");
|
|
2178
|
-
} else {
|
|
2416
|
+
} else if (validationResult.valid) {
|
|
2417
|
+
console.log(`
|
|
2418
|
+
\u26A0\uFE0F ${validationResult.stale.length} translations may be outdated.
|
|
2419
|
+
`);
|
|
2420
|
+
}
|
|
2421
|
+
if (hasWorkToFill) {
|
|
2179
2422
|
const totalMissing = validationResult.missing.length + validationResult.empty.length + validationResult.orphaned.length;
|
|
2180
2423
|
if (autoFill) {
|
|
2181
2424
|
if (!apiKey) {
|
|
@@ -2185,29 +2428,38 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2185
2428
|
console.log(`Set ${envVarName} or pass --api-key to enable auto-fill.
|
|
2186
2429
|
`);
|
|
2187
2430
|
} else {
|
|
2188
|
-
|
|
2189
|
-
\u{1F916}
|
|
2431
|
+
if (force) {
|
|
2432
|
+
console.log("\n\u{1F916} Force re-translating all keys...\n");
|
|
2433
|
+
} else {
|
|
2434
|
+
const staleCount = retranslateChanged ? validationResult.stale.length : 0;
|
|
2435
|
+
console.log(`
|
|
2436
|
+
\u{1F916} Auto-filling ${totalMissing} missing and ${staleCount} changed translations...
|
|
2190
2437
|
`);
|
|
2438
|
+
}
|
|
2191
2439
|
await autoFillTranslations(projectRoot, {
|
|
2192
2440
|
apiKey,
|
|
2193
2441
|
limit,
|
|
2194
2442
|
concurrency,
|
|
2195
2443
|
language,
|
|
2196
2444
|
dryRun,
|
|
2197
|
-
delayMs: 50
|
|
2445
|
+
delayMs: 50,
|
|
2446
|
+
retranslateChanged,
|
|
2447
|
+
force
|
|
2198
2448
|
});
|
|
2199
2449
|
if (!dryRun) {
|
|
2200
2450
|
console.log("\n\u{1F50D} Re-validating after auto-fill...\n");
|
|
2201
2451
|
const revalidation = validateTranslations(projectRoot);
|
|
2202
|
-
if (revalidation.valid) {
|
|
2452
|
+
if (revalidation.valid && !revalidation.stale.length) {
|
|
2203
2453
|
console.log("\n\u2705 All translations are now complete!\n");
|
|
2204
2454
|
}
|
|
2205
2455
|
}
|
|
2206
2456
|
}
|
|
2207
|
-
} else {
|
|
2457
|
+
} else if (!validationResult.valid) {
|
|
2208
2458
|
console.log(`
|
|
2209
2459
|
\u{1F4A1} Tip: Run with --auto-fill to automatically translate missing keys.
|
|
2210
2460
|
`);
|
|
2461
|
+
} else if (validationResult.stale.length > 0) {
|
|
2462
|
+
console.log("\n\u{1F4A1} Tip: Run with --auto-fill --retranslate-changed to update outdated translations.\n");
|
|
2211
2463
|
}
|
|
2212
2464
|
}
|
|
2213
2465
|
if (!skipTypes && !dryRun) {
|
|
@@ -2233,9 +2485,17 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2233
2485
|
if (validationResult.orphaned.length > 0) {
|
|
2234
2486
|
console.log(`\u26A0\uFE0F ${validationResult.orphaned.length} orphaned translations (will be auto-removed on next sync)`);
|
|
2235
2487
|
}
|
|
2488
|
+
if (validationResult.stale.length > 0) {
|
|
2489
|
+
console.log(`\u26A0\uFE0F ${validationResult.stale.length} potentially outdated translations`);
|
|
2490
|
+
}
|
|
2236
2491
|
console.log("\nNext steps:");
|
|
2237
2492
|
console.log(" 1. Add missing translations manually, or");
|
|
2238
2493
|
console.log(" 2. Run with --auto-fill to translate automatically");
|
|
2494
|
+
} else if (validationResult.valid && validationResult.stale.length > 0 && !autoFill) {
|
|
2495
|
+
console.log(`
|
|
2496
|
+
\u26A0\uFE0F ${validationResult.stale.length} potentially outdated translations`);
|
|
2497
|
+
console.log("\nNext steps:");
|
|
2498
|
+
console.log(" Run with --auto-fill --retranslate-changed to update them");
|
|
2239
2499
|
} else if (validationResult.valid) {
|
|
2240
2500
|
console.log("\n\u2705 All systems ready!");
|
|
2241
2501
|
}
|
|
@@ -2250,6 +2510,7 @@ async function manageTranslations(projectRoot = process.cwd(), options = {}) {
|
|
|
2250
2510
|
// src/translations/index.ts
|
|
2251
2511
|
init_schema();
|
|
2252
2512
|
init_types();
|
|
2513
|
+
init_metadata();
|
|
2253
2514
|
init_utils();
|
|
2254
2515
|
export {
|
|
2255
2516
|
DEEPL_LANGUAGES,
|
|
@@ -2257,6 +2518,7 @@ export {
|
|
|
2257
2518
|
DEFAULT_LANGUAGES,
|
|
2258
2519
|
GOOGLE_LANGUAGES,
|
|
2259
2520
|
GoogleTranslateProvider,
|
|
2521
|
+
METADATA_FILE_NAME,
|
|
2260
2522
|
SUPPORTED_LANGUAGES,
|
|
2261
2523
|
TRANSLATION_CONFIG_SCHEMA,
|
|
2262
2524
|
TRANSLATION_PROVIDERS,
|
|
@@ -2265,6 +2527,8 @@ export {
|
|
|
2265
2527
|
autoFillTranslations,
|
|
2266
2528
|
createEmptyTranslationStructure,
|
|
2267
2529
|
detectExistingTranslations,
|
|
2530
|
+
emptyMetadata,
|
|
2531
|
+
ensureBaselineMetadata,
|
|
2268
2532
|
ensureTranslationsStructure,
|
|
2269
2533
|
extractPluralBaseKeys,
|
|
2270
2534
|
extractVariables,
|
|
@@ -2274,11 +2538,14 @@ export {
|
|
|
2274
2538
|
generateTranslationTypes,
|
|
2275
2539
|
getAvailableLanguages,
|
|
2276
2540
|
getFallbackMappings,
|
|
2541
|
+
getMetadataPath,
|
|
2277
2542
|
getMissingForLanguage,
|
|
2278
2543
|
getNamespaces,
|
|
2544
|
+
getSourceHash,
|
|
2279
2545
|
getSupportedLanguages,
|
|
2280
2546
|
getTranslationProvider,
|
|
2281
2547
|
hasInterpolation,
|
|
2548
|
+
hashSourceValue,
|
|
2282
2549
|
initTranslations,
|
|
2283
2550
|
initTranslationsInteractive,
|
|
2284
2551
|
isNestedObject,
|
|
@@ -2289,10 +2556,14 @@ export {
|
|
|
2289
2556
|
loadConfig,
|
|
2290
2557
|
logLanguageFallback,
|
|
2291
2558
|
manageTranslations,
|
|
2559
|
+
metadataExists,
|
|
2292
2560
|
printUnusedKeysResult,
|
|
2561
|
+
pruneMetadata,
|
|
2562
|
+
readMetadata,
|
|
2293
2563
|
readTranslations,
|
|
2294
2564
|
resetTranslationProvider,
|
|
2295
2565
|
resolveLanguageWithFallback,
|
|
2566
|
+
setSourceHash,
|
|
2296
2567
|
setTranslationProvider,
|
|
2297
2568
|
sortKeys,
|
|
2298
2569
|
syncTranslationStructure,
|
|
@@ -2303,6 +2574,7 @@ export {
|
|
|
2303
2574
|
validateLanguagesForProvider,
|
|
2304
2575
|
validateTranslations,
|
|
2305
2576
|
validateVariables,
|
|
2577
|
+
writeMetadata,
|
|
2306
2578
|
writeTranslation
|
|
2307
2579
|
};
|
|
2308
2580
|
//# sourceMappingURL=index.js.map
|