@mariokreitz/langsync 0.3.0 → 0.4.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 +1 -1
- package/dist/cli.js +156 -8
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -258,7 +258,7 @@ var LangSyncConfigSchema = z.object({
|
|
|
258
258
|
ai: z.object({
|
|
259
259
|
provider: z.enum(["openai", "deepl", "anthropic", "gemini"]).default("openai").describe("AI translation provider."),
|
|
260
260
|
apiKey: z.string().optional().describe("API key. Falls back to the provider-specific env var."),
|
|
261
|
-
model: z.string().optional().describe("Provider model id (e.g. gpt-
|
|
261
|
+
model: z.string().optional().describe("Provider model id (e.g. gpt-5-mini).")
|
|
262
262
|
}).optional().describe("AI translation settings.")
|
|
263
263
|
});
|
|
264
264
|
async function loadConfig(cwd = process.cwd()) {
|
|
@@ -676,7 +676,7 @@ function registerImportCommand(program) {
|
|
|
676
676
|
}
|
|
677
677
|
|
|
678
678
|
// ../ai-engine/dist/index.js
|
|
679
|
-
var DEFAULT_MODEL = "gpt-
|
|
679
|
+
var DEFAULT_MODEL = "gpt-5-mini";
|
|
680
680
|
var ENDPOINT = "https://api.openai.com/v1/chat/completions";
|
|
681
681
|
var OpenAIAdapter = class {
|
|
682
682
|
provider = "openai";
|
|
@@ -724,6 +724,145 @@ var OpenAIAdapter = class {
|
|
|
724
724
|
return content;
|
|
725
725
|
}
|
|
726
726
|
};
|
|
727
|
+
var FREE_ENDPOINT = "https://api-free.deepl.com/v2/translate";
|
|
728
|
+
var PRO_ENDPOINT = "https://api.deepl.com/v2/translate";
|
|
729
|
+
var FREE_KEY_SUFFIX = ":fx";
|
|
730
|
+
function toDeepLLang(locale) {
|
|
731
|
+
return locale.split("-")[0].toUpperCase();
|
|
732
|
+
}
|
|
733
|
+
var DeepLAdapter = class {
|
|
734
|
+
provider = "deepl";
|
|
735
|
+
apiKey;
|
|
736
|
+
endpoint;
|
|
737
|
+
fetchImpl;
|
|
738
|
+
constructor(options = {}) {
|
|
739
|
+
const apiKey = options.apiKey ?? process.env.DEEPL_API_KEY;
|
|
740
|
+
if (!apiKey) {
|
|
741
|
+
throw new Error(
|
|
742
|
+
"DeepL API key missing. Set `ai.apiKey` in your config or the DEEPL_API_KEY env var."
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
this.apiKey = apiKey;
|
|
746
|
+
const useFreeTier = options.useFreeTier ?? apiKey.endsWith(FREE_KEY_SUFFIX);
|
|
747
|
+
this.endpoint = useFreeTier ? FREE_ENDPOINT : PRO_ENDPOINT;
|
|
748
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
749
|
+
}
|
|
750
|
+
async translate(request) {
|
|
751
|
+
const response = await this.fetchImpl(this.endpoint, {
|
|
752
|
+
method: "POST",
|
|
753
|
+
headers: {
|
|
754
|
+
"content-type": "application/json",
|
|
755
|
+
authorization: `DeepL-Auth-Key ${this.apiKey}`
|
|
756
|
+
},
|
|
757
|
+
body: JSON.stringify({
|
|
758
|
+
text: [request.text],
|
|
759
|
+
source_lang: toDeepLLang(request.sourceLocale),
|
|
760
|
+
target_lang: toDeepLLang(request.targetLocale)
|
|
761
|
+
})
|
|
762
|
+
});
|
|
763
|
+
if (!response.ok) {
|
|
764
|
+
throw new Error(`DeepL request failed: ${response.status} ${response.statusText}`);
|
|
765
|
+
}
|
|
766
|
+
const data = await response.json();
|
|
767
|
+
const content = data.translations?.[0]?.text?.trim();
|
|
768
|
+
if (!content) {
|
|
769
|
+
throw new Error("DeepL returned an empty translation.");
|
|
770
|
+
}
|
|
771
|
+
return content;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var DEFAULT_MODEL2 = "claude-haiku-4-5";
|
|
775
|
+
var ENDPOINT2 = "https://api.anthropic.com/v1/messages";
|
|
776
|
+
var ANTHROPIC_VERSION = "2023-06-01";
|
|
777
|
+
var MAX_TOKENS = 1024;
|
|
778
|
+
var AnthropicAdapter = class {
|
|
779
|
+
provider = "anthropic";
|
|
780
|
+
apiKey;
|
|
781
|
+
model;
|
|
782
|
+
fetchImpl;
|
|
783
|
+
constructor(options = {}) {
|
|
784
|
+
const apiKey = options.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
785
|
+
if (!apiKey) {
|
|
786
|
+
throw new Error(
|
|
787
|
+
"Anthropic API key missing. Set `ai.apiKey` in your config or the ANTHROPIC_API_KEY env var."
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
this.apiKey = apiKey;
|
|
791
|
+
this.model = options.model ?? DEFAULT_MODEL2;
|
|
792
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
793
|
+
}
|
|
794
|
+
async translate(request) {
|
|
795
|
+
const response = await this.fetchImpl(ENDPOINT2, {
|
|
796
|
+
method: "POST",
|
|
797
|
+
headers: {
|
|
798
|
+
"content-type": "application/json",
|
|
799
|
+
"x-api-key": this.apiKey,
|
|
800
|
+
"anthropic-version": ANTHROPIC_VERSION
|
|
801
|
+
},
|
|
802
|
+
body: JSON.stringify({
|
|
803
|
+
model: this.model,
|
|
804
|
+
max_tokens: MAX_TOKENS,
|
|
805
|
+
system: `You are a professional software localization engine. Translate the user message from ${request.sourceLocale} to ${request.targetLocale}. Preserve placeholders, ICU syntax, and surrounding punctuation. Respond with the translation only, no quotes or commentary.`,
|
|
806
|
+
messages: [{ role: "user", content: request.text }]
|
|
807
|
+
})
|
|
808
|
+
});
|
|
809
|
+
if (!response.ok) {
|
|
810
|
+
throw new Error(`Anthropic request failed: ${response.status} ${response.statusText}`);
|
|
811
|
+
}
|
|
812
|
+
const data = await response.json();
|
|
813
|
+
const content = data.content?.find((block) => block.type === "text" || block.text)?.text?.trim();
|
|
814
|
+
if (!content) {
|
|
815
|
+
throw new Error("Anthropic returned an empty translation.");
|
|
816
|
+
}
|
|
817
|
+
return content;
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
var DEFAULT_MODEL3 = "gemini-3-flash";
|
|
821
|
+
var BASE_URL = "https://generativelanguage.googleapis.com/v1beta/models";
|
|
822
|
+
var GeminiAdapter = class {
|
|
823
|
+
provider = "gemini";
|
|
824
|
+
apiKey;
|
|
825
|
+
model;
|
|
826
|
+
fetchImpl;
|
|
827
|
+
constructor(options = {}) {
|
|
828
|
+
const apiKey = options.apiKey ?? process.env.GEMINI_API_KEY;
|
|
829
|
+
if (!apiKey) {
|
|
830
|
+
throw new Error(
|
|
831
|
+
"Gemini API key missing. Set `ai.apiKey` in your config or the GEMINI_API_KEY env var."
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
this.apiKey = apiKey;
|
|
835
|
+
this.model = options.model ?? DEFAULT_MODEL3;
|
|
836
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
837
|
+
}
|
|
838
|
+
async translate(request) {
|
|
839
|
+
const url = `${BASE_URL}/${this.model}:generateContent?key=${this.apiKey}`;
|
|
840
|
+
const response = await this.fetchImpl(url, {
|
|
841
|
+
method: "POST",
|
|
842
|
+
headers: { "content-type": "application/json" },
|
|
843
|
+
body: JSON.stringify({
|
|
844
|
+
systemInstruction: {
|
|
845
|
+
parts: [
|
|
846
|
+
{
|
|
847
|
+
text: `You are a professional software localization engine. Translate the user message from ${request.sourceLocale} to ${request.targetLocale}. Preserve placeholders, ICU syntax, and surrounding punctuation. Respond with the translation only, no quotes or commentary.`
|
|
848
|
+
}
|
|
849
|
+
]
|
|
850
|
+
},
|
|
851
|
+
contents: [{ parts: [{ text: request.text }] }],
|
|
852
|
+
generationConfig: { temperature: 0 }
|
|
853
|
+
})
|
|
854
|
+
});
|
|
855
|
+
if (!response.ok) {
|
|
856
|
+
throw new Error(`Gemini request failed: ${response.status} ${response.statusText}`);
|
|
857
|
+
}
|
|
858
|
+
const data = await response.json();
|
|
859
|
+
const content = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim();
|
|
860
|
+
if (!content) {
|
|
861
|
+
throw new Error("Gemini returned an empty translation.");
|
|
862
|
+
}
|
|
863
|
+
return content;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
727
866
|
var RELEASED_PROVIDERS = ["openai"];
|
|
728
867
|
var ALL_PROVIDERS = ["openai", "deepl", "anthropic", "gemini"];
|
|
729
868
|
function experimentalEnabled() {
|
|
@@ -745,8 +884,16 @@ function createAdapter(options) {
|
|
|
745
884
|
switch (provider) {
|
|
746
885
|
case "openai":
|
|
747
886
|
return new OpenAIAdapter(rest);
|
|
748
|
-
|
|
749
|
-
|
|
887
|
+
case "deepl":
|
|
888
|
+
return new DeepLAdapter(rest);
|
|
889
|
+
case "anthropic":
|
|
890
|
+
return new AnthropicAdapter(rest);
|
|
891
|
+
case "gemini":
|
|
892
|
+
return new GeminiAdapter(rest);
|
|
893
|
+
default: {
|
|
894
|
+
const exhaustive = provider;
|
|
895
|
+
throw new Error(`AI provider "${String(exhaustive)}" has no adapter implementation yet.`);
|
|
896
|
+
}
|
|
750
897
|
}
|
|
751
898
|
}
|
|
752
899
|
function isEmpty(value) {
|
|
@@ -781,7 +928,7 @@ async function runTranslate(options) {
|
|
|
781
928
|
const adapter = createAdapter({
|
|
782
929
|
provider,
|
|
783
930
|
apiKey: config.ai?.apiKey,
|
|
784
|
-
model: config.ai?.model
|
|
931
|
+
model: options.model ?? config.ai?.model
|
|
785
932
|
});
|
|
786
933
|
const files = await loadLocaleFiles({
|
|
787
934
|
cwd: options.cwd,
|
|
@@ -817,13 +964,14 @@ async function runTranslate(options) {
|
|
|
817
964
|
|
|
818
965
|
// src/commands/translate.ts
|
|
819
966
|
function registerTranslateCommand(program) {
|
|
820
|
-
program.command("translate").description("Fill empty values in non-reference locales using an AI provider.").option("--provider <provider>", "Override the configured AI provider.").option("--dry-run", "Report what would be translated without writing files.", false).action(async (flags) => {
|
|
967
|
+
program.command("translate").description("Fill empty values in non-reference locales using an AI provider.").option("--provider <provider>", "Override the configured AI provider.").option("--model <model>", "Override the configured provider model.").option("--dry-run", "Report what would be translated without writing files.", false).action(async (flags) => {
|
|
821
968
|
try {
|
|
822
969
|
const cwd = process.cwd();
|
|
823
970
|
const result = await runTranslate({
|
|
824
971
|
cwd,
|
|
825
972
|
dryRun: flags.dryRun,
|
|
826
|
-
provider: flags.provider
|
|
973
|
+
provider: flags.provider,
|
|
974
|
+
model: flags.model
|
|
827
975
|
});
|
|
828
976
|
const totals = Object.values(result.translatedByLocale).reduce(
|
|
829
977
|
(sum, keys) => sum + keys.length,
|
|
@@ -931,7 +1079,7 @@ function registerWatchCommand(program) {
|
|
|
931
1079
|
}
|
|
932
1080
|
|
|
933
1081
|
// src/cli.ts
|
|
934
|
-
var VERSION = "0.
|
|
1082
|
+
var VERSION = "0.4.0" ;
|
|
935
1083
|
async function main() {
|
|
936
1084
|
assertNodeVersion(22);
|
|
937
1085
|
const program = new Command();
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ z.object({
|
|
|
16
16
|
ai: z.object({
|
|
17
17
|
provider: z.enum(["openai", "deepl", "anthropic", "gemini"]).default("openai").describe("AI translation provider."),
|
|
18
18
|
apiKey: z.string().optional().describe("API key. Falls back to the provider-specific env var."),
|
|
19
|
-
model: z.string().optional().describe("Provider model id (e.g. gpt-
|
|
19
|
+
model: z.string().optional().describe("Provider model id (e.g. gpt-5-mini).")
|
|
20
20
|
}).optional().describe("AI translation settings.")
|
|
21
21
|
});
|
|
22
22
|
function defineConfig(config) {
|