@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 CHANGED
@@ -89,7 +89,7 @@ export default defineConfig({
89
89
  },
90
90
  ai: {
91
91
  provider: 'openai',
92
- model: 'gpt-4o-mini',
92
+ model: 'gpt-5-mini',
93
93
  },
94
94
  });
95
95
  ```
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-4o-mini).")
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-4o-mini";
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
- default:
749
- throw new Error(`AI provider "${provider}" has no adapter implementation yet.`);
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.3.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-4o-mini).")
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mariokreitz/langsync",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Modern localization workflow tooling for TypeScript applications.",
5
5
  "keywords": [
6
6
  "i18n",