@lingo.dev/compiler 0.1.2 → 0.1.4

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.
Files changed (58) hide show
  1. package/build/plugin/build-translator.cjs +3 -3
  2. package/build/plugin/build-translator.mjs +3 -3
  3. package/build/plugin/build-translator.mjs.map +1 -1
  4. package/build/plugin/next.cjs +3 -3
  5. package/build/plugin/next.d.cts.map +1 -1
  6. package/build/plugin/next.d.mts.map +1 -1
  7. package/build/plugin/next.mjs +3 -3
  8. package/build/plugin/next.mjs.map +1 -1
  9. package/build/plugin/unplugin.cjs +81 -2
  10. package/build/plugin/unplugin.d.cts.map +1 -1
  11. package/build/plugin/unplugin.d.mts.map +1 -1
  12. package/build/plugin/unplugin.mjs +81 -2
  13. package/build/plugin/unplugin.mjs.map +1 -1
  14. package/build/react/server/ServerLingoProvider.d.cts +2 -2
  15. package/build/react/shared/LingoProvider.d.cts +2 -2
  16. package/build/react/shared/LocaleSwitcher.d.cts +2 -2
  17. package/build/translation-server/translation-server.cjs +7 -17
  18. package/build/translation-server/translation-server.mjs +7 -17
  19. package/build/translation-server/translation-server.mjs.map +1 -1
  20. package/build/translators/cache-factory.mjs.map +1 -1
  21. package/build/translators/lingo/model-factory.cjs +5 -10
  22. package/build/translators/lingo/model-factory.mjs +5 -10
  23. package/build/translators/lingo/model-factory.mjs.map +1 -1
  24. package/build/translators/lingo/provider-details.cjs +69 -0
  25. package/build/translators/lingo/provider-details.mjs +69 -0
  26. package/build/translators/lingo/provider-details.mjs.map +1 -0
  27. package/build/translators/lingo/{service.cjs → translator.cjs} +11 -13
  28. package/build/translators/lingo/{service.mjs → translator.mjs} +12 -14
  29. package/build/translators/lingo/translator.mjs.map +1 -0
  30. package/build/translators/memory-cache.cjs +47 -0
  31. package/build/translators/memory-cache.mjs +47 -0
  32. package/build/translators/memory-cache.mjs.map +1 -0
  33. package/build/translators/pluralization/service.cjs +19 -44
  34. package/build/translators/pluralization/service.mjs +19 -44
  35. package/build/translators/pluralization/service.mjs.map +1 -1
  36. package/build/translators/pseudotranslator/index.cjs +2 -10
  37. package/build/translators/pseudotranslator/index.mjs +2 -10
  38. package/build/translators/pseudotranslator/index.mjs.map +1 -1
  39. package/build/translators/translation-service.cjs +55 -57
  40. package/build/translators/translation-service.mjs +55 -57
  41. package/build/translators/translation-service.mjs.map +1 -1
  42. package/build/utils/observability.cjs +84 -0
  43. package/build/utils/observability.mjs +83 -0
  44. package/build/utils/observability.mjs.map +1 -0
  45. package/build/utils/rc.cjs +21 -0
  46. package/build/utils/rc.mjs +17 -0
  47. package/build/utils/rc.mjs.map +1 -0
  48. package/build/utils/repository-id.cjs +64 -0
  49. package/build/utils/repository-id.mjs +64 -0
  50. package/build/utils/repository-id.mjs.map +1 -0
  51. package/build/utils/tracking-events.cjs +28 -0
  52. package/build/utils/tracking-events.mjs +25 -0
  53. package/build/utils/tracking-events.mjs.map +1 -0
  54. package/package.json +12 -8
  55. package/build/translators/lingo/service.mjs.map +0 -1
  56. package/build/translators/translator-factory.cjs +0 -49
  57. package/build/translators/translator-factory.mjs +0 -50
  58. package/build/translators/translator-factory.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"model-factory.mjs","names":["path","providerDetails: Record<string, ProviderConfig>","keys: ValidatedApiKeys","missingKeys: Array<{ provider: string; envVar: string }>","providersToValidate: string[]"],"sources":["../../../src/translators/lingo/model-factory.ts"],"sourcesContent":["/**\n * Shared utilities for creating AI model instances\n */\n\nimport { createGroq } from \"@ai-sdk/groq\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { createOpenRouter } from \"@openrouter/ai-sdk-provider\";\nimport { ollama } from \"ai-sdk-ollama\";\nimport { createMistral } from \"@ai-sdk/mistral\";\nimport type { LanguageModel } from \"ai\";\nimport * as dotenv from \"dotenv\";\nimport * as path from \"path\";\n\nexport type LocaleModel = {\n provider: string;\n name: string;\n};\n\nexport function getKeyFromEnv(envVarName: string): string | undefined {\n if (process.env[envVarName]) {\n return process.env[envVarName];\n }\n\n const projectRoot = process.cwd();\n\n const result = dotenv.config({\n path: [\n path.resolve(projectRoot, \".env\"),\n path.resolve(projectRoot, \".env.local\"),\n path.resolve(projectRoot, \".env.development\"),\n ],\n });\n\n return result?.parsed?.[envVarName];\n}\n\n/**\n * Pre-validated API keys for all providers\n * Keys are fetched and validated once at initialization\n */\nexport type ValidatedApiKeys = Record<string, string>;\n\n/**\n * Provider configuration including env var names and requirements\n */\ntype ProviderConfig = {\n name: string; // Display name (e.g., \"Groq\", \"Google\")\n apiKeyEnvVar?: string; // Environment variable name (e.g., \"GROQ_API_KEY\")\n apiKeyConfigKey?: string; // Config key if applicable (e.g., \"llm.groqApiKey\")\n getKeyLink: string; // Link to get API key\n docsLink: string; // Link to API docs for troubleshooting\n};\n\nexport const providerDetails: Record<string, ProviderConfig> = {\n groq: {\n name: \"Groq\",\n apiKeyEnvVar: \"GROQ_API_KEY\",\n apiKeyConfigKey: \"llm.groqApiKey\",\n getKeyLink: \"https://groq.com\",\n docsLink: \"https://console.groq.com/docs/errors\",\n },\n google: {\n name: \"Google\",\n apiKeyEnvVar: \"GOOGLE_API_KEY\",\n apiKeyConfigKey: \"llm.googleApiKey\",\n getKeyLink: \"https://ai.google.dev/\",\n docsLink: \"https://ai.google.dev/gemini-api/docs/troubleshooting\",\n },\n openai: {\n name: \"OpenAI\",\n apiKeyEnvVar: \"OPENAI_API_KEY\",\n apiKeyConfigKey: \"llm.openaiApiKey\",\n getKeyLink: \"https://platform.openai.com/account/api-keys\",\n docsLink: \"https://platform.openai.com/docs\",\n },\n anthropic: {\n name: \"Anthropic\",\n apiKeyEnvVar: \"ANTHROPIC_API_KEY\",\n apiKeyConfigKey: \"llm.anthropicApiKey\",\n getKeyLink: \"https://console.anthropic.com/get-api-key\",\n docsLink: \"https://console.anthropic.com/docs\",\n },\n openrouter: {\n name: \"OpenRouter\",\n apiKeyEnvVar: \"OPENROUTER_API_KEY\",\n apiKeyConfigKey: \"llm.openrouterApiKey\",\n getKeyLink: \"https://openrouter.ai\",\n docsLink: \"https://openrouter.ai/docs\",\n },\n ollama: {\n name: \"Ollama\",\n apiKeyEnvVar: undefined, // Ollama doesn't require an API key\n apiKeyConfigKey: undefined, // Ollama doesn't require an API key\n getKeyLink: \"https://ollama.com/download\",\n docsLink: \"https://github.com/ollama/ollama/tree/main/docs\",\n },\n mistral: {\n name: \"Mistral\",\n apiKeyEnvVar: \"MISTRAL_API_KEY\",\n apiKeyConfigKey: \"llm.mistralApiKey\",\n getKeyLink: \"https://console.mistral.ai\",\n docsLink: \"https://docs.mistral.ai\",\n },\n \"lingo.dev\": {\n name: \"Lingo.dev\",\n apiKeyEnvVar: \"LINGODOTDEV_API_KEY\",\n apiKeyConfigKey: \"auth.apiKey\",\n getKeyLink: \"https://lingo.dev\",\n docsLink: \"https://lingo.dev/docs\",\n },\n};\n\n/**\n * Get provider and model for a specific locale pair\n */\nexport function getLocaleModel(\n localeModels: Record<string, string>,\n sourceLocale: string,\n targetLocale: string,\n): LocaleModel | undefined {\n const localeKeys = [\n `${sourceLocale}:${targetLocale}`,\n `*:${targetLocale}`,\n `${sourceLocale}:*`,\n \"*:*\",\n ];\n\n const modelKey = localeKeys.find((key) => key in localeModels);\n if (!modelKey) {\n return undefined;\n }\n\n const value = localeModels[modelKey];\n if (!value) {\n return undefined;\n }\n\n return parseModelString(value);\n}\n\n/**\n * Parse provider and model from model string\n * Format: \"provider:model\" (e.g., \"groq:llama3-8b-8192\")\n *\n * @param modelString Model string to parse\n * @returns Object with provider and model\n * @throws Error if format is invalid\n */\nexport function parseModelString(modelString: string): LocaleModel | undefined {\n // Split on first colon only\n const [provider, name] = modelString.split(\":\", 2);\n\n if (!provider || !name) {\n return undefined;\n }\n\n return { provider, name };\n}\n\n/**\n * Validate and fetch all necessary API keys for the given configuration\n * This should be called once at initialization time\n *\n * @param config Model configuration (\"lingo.dev\" or locale-pair mapping)\n * @returns Validated API keys (provider ID -> API key)\n * @throws Error if required keys are missing\n */\nexport function validateAndGetApiKeys(\n config: \"lingo.dev\" | Record<string, string>,\n): ValidatedApiKeys {\n const keys: ValidatedApiKeys = {};\n const missingKeys: Array<{ provider: string; envVar: string }> = [];\n\n // Determine which providers are configured\n let providersToValidate: string[];\n\n if (config === \"lingo.dev\") {\n // Only need lingo.dev provider\n providersToValidate = [\"lingo.dev\"];\n } else {\n // Extract unique providers from model strings\n const providerSet = new Set<string>();\n Object.values(config).forEach((modelString) => {\n const model = parseModelString(modelString);\n if (model) {\n providerSet.add(model.provider);\n }\n });\n providersToValidate = Array.from(providerSet);\n }\n\n // Validate and fetch keys for each provider\n for (const provider of providersToValidate) {\n const providerConfig = providerDetails[provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Unknown provider \"${provider}\". Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Skip providers that don't require keys (like Ollama)\n if (!providerConfig.apiKeyEnvVar) {\n continue;\n }\n\n const key = getKeyFromEnv(providerConfig.apiKeyEnvVar);\n if (key) {\n keys[provider] = key;\n } else {\n missingKeys.push({\n provider: providerConfig.name,\n envVar: providerConfig.apiKeyEnvVar,\n });\n }\n }\n\n // If any keys are missing, throw with detailed error\n if (missingKeys.length > 0) {\n const errorLines = missingKeys.map(\n ({ provider, envVar }) => ` - ${provider}: ${envVar}`,\n );\n throw new Error(\n `⚠️ Missing API keys for configured providers:\\n${errorLines.join(\"\\n\")}\\n\\nPlease set the required environment variables.`,\n );\n }\n\n return keys;\n}\n\n/**\n * Create AI model instance from provider and model ID\n *\n * @param model Provider name (groq, google, openrouter, ollama, mistral) and model identifier\n * @param validatedKeys Pre-validated API keys from validateAndFetchApiKeys()\n * @returns LanguageModel instance\n * @throws Error if provider is not supported or API key is missing\n */\nexport function createAiModel(\n model: LocaleModel,\n validatedKeys: ValidatedApiKeys,\n): LanguageModel {\n const providerConfig = providerDetails[model.provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Provider \"${model.provider}\" is not supported. Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Get API key if required\n const apiKey = providerConfig.apiKeyEnvVar\n ? validatedKeys[model.provider]\n : undefined;\n\n // Verify key is present for providers that require it\n if (providerConfig.apiKeyEnvVar && !apiKey) {\n throw new Error(\n `⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\\n\\n` +\n `This should not happen if validateAndFetchApiKeys() was called. Please restart the service.`,\n );\n }\n\n // Create the appropriate model instance\n switch (model.provider) {\n case \"groq\":\n return createGroq({ apiKey: apiKey! })(model.name);\n\n case \"google\":\n return createGoogleGenerativeAI({ apiKey: apiKey! })(model.name);\n\n case \"openrouter\":\n return createOpenRouter({ apiKey: apiKey! })(model.name);\n\n case \"ollama\":\n return ollama(model.name);\n\n case \"mistral\":\n return createMistral({ apiKey: apiKey! })(model.name);\n\n default:\n // This should be unreachable due to check above\n throw new Error(`⚠️ Provider \"${model.provider}\" is not implemented`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model-factory.mjs","names":["path","providerDetails: Record<string, ProviderConfig>","keys: ValidatedApiKeys","missingProviders: string[]","providersToValidate: string[]"],"sources":["../../../src/translators/lingo/model-factory.ts"],"sourcesContent":["/**\n * Shared utilities for creating AI model instances\n */\n\nimport { createGroq } from \"@ai-sdk/groq\";\nimport { createGoogleGenerativeAI } from \"@ai-sdk/google\";\nimport { createOpenRouter } from \"@openrouter/ai-sdk-provider\";\nimport { ollama } from \"ai-sdk-ollama\";\nimport { createMistral } from \"@ai-sdk/mistral\";\nimport type { LanguageModel } from \"ai\";\nimport * as dotenv from \"dotenv\";\nimport * as path from \"path\";\nimport { formatNoApiKeysError } from \"./provider-details\";\n\nexport type LocaleModel = {\n provider: string;\n name: string;\n};\n\nexport function getKeyFromEnv(envVarName: string): string | undefined {\n if (process.env[envVarName]) {\n return process.env[envVarName];\n }\n\n const projectRoot = process.cwd();\n\n const result = dotenv.config({\n path: [\n path.resolve(projectRoot, \".env\"),\n path.resolve(projectRoot, \".env.local\"),\n path.resolve(projectRoot, \".env.development\"),\n ],\n });\n\n return result?.parsed?.[envVarName];\n}\n\n/**\n * Pre-validated API keys for all providers\n * Keys are fetched and validated once at initialization\n */\nexport type ValidatedApiKeys = Record<string, string>;\n\n/**\n * Provider configuration including env var names and requirements\n */\ntype ProviderConfig = {\n name: string; // Display name (e.g., \"Groq\", \"Google\")\n apiKeyEnvVar?: string; // Environment variable name (e.g., \"GROQ_API_KEY\")\n apiKeyConfigKey?: string; // Config key if applicable (e.g., \"llm.groqApiKey\")\n getKeyLink: string; // Link to get API key\n docsLink: string; // Link to API docs for troubleshooting\n};\n\nexport const providerDetails: Record<string, ProviderConfig> = {\n groq: {\n name: \"Groq\",\n apiKeyEnvVar: \"GROQ_API_KEY\",\n apiKeyConfigKey: \"llm.groqApiKey\",\n getKeyLink: \"https://groq.com\",\n docsLink: \"https://console.groq.com/docs/errors\",\n },\n google: {\n name: \"Google\",\n apiKeyEnvVar: \"GOOGLE_API_KEY\",\n apiKeyConfigKey: \"llm.googleApiKey\",\n getKeyLink: \"https://ai.google.dev/\",\n docsLink: \"https://ai.google.dev/gemini-api/docs/troubleshooting\",\n },\n openai: {\n name: \"OpenAI\",\n apiKeyEnvVar: \"OPENAI_API_KEY\",\n apiKeyConfigKey: \"llm.openaiApiKey\",\n getKeyLink: \"https://platform.openai.com/account/api-keys\",\n docsLink: \"https://platform.openai.com/docs\",\n },\n anthropic: {\n name: \"Anthropic\",\n apiKeyEnvVar: \"ANTHROPIC_API_KEY\",\n apiKeyConfigKey: \"llm.anthropicApiKey\",\n getKeyLink: \"https://console.anthropic.com/get-api-key\",\n docsLink: \"https://console.anthropic.com/docs\",\n },\n openrouter: {\n name: \"OpenRouter\",\n apiKeyEnvVar: \"OPENROUTER_API_KEY\",\n apiKeyConfigKey: \"llm.openrouterApiKey\",\n getKeyLink: \"https://openrouter.ai\",\n docsLink: \"https://openrouter.ai/docs\",\n },\n ollama: {\n name: \"Ollama\",\n apiKeyEnvVar: undefined, // Ollama doesn't require an API key\n apiKeyConfigKey: undefined, // Ollama doesn't require an API key\n getKeyLink: \"https://ollama.com/download\",\n docsLink: \"https://github.com/ollama/ollama/tree/main/docs\",\n },\n mistral: {\n name: \"Mistral\",\n apiKeyEnvVar: \"MISTRAL_API_KEY\",\n apiKeyConfigKey: \"llm.mistralApiKey\",\n getKeyLink: \"https://console.mistral.ai\",\n docsLink: \"https://docs.mistral.ai\",\n },\n \"lingo.dev\": {\n name: \"Lingo.dev\",\n apiKeyEnvVar: \"LINGODOTDEV_API_KEY\",\n apiKeyConfigKey: \"auth.apiKey\",\n getKeyLink: \"https://lingo.dev\",\n docsLink: \"https://lingo.dev/docs\",\n },\n};\n\n/**\n * Get provider and model for a specific locale pair\n */\nexport function getLocaleModel(\n localeModels: Record<string, string>,\n sourceLocale: string,\n targetLocale: string,\n): LocaleModel | undefined {\n const localeKeys = [\n `${sourceLocale}:${targetLocale}`,\n `*:${targetLocale}`,\n `${sourceLocale}:*`,\n \"*:*\",\n ];\n\n const modelKey = localeKeys.find((key) => key in localeModels);\n if (!modelKey) {\n return undefined;\n }\n\n const value = localeModels[modelKey];\n if (!value) {\n return undefined;\n }\n\n return parseModelString(value);\n}\n\n/**\n * Parse provider and model from model string\n * Format: \"provider:model\" (e.g., \"groq:llama3-8b-8192\")\n *\n * @param modelString Model string to parse\n * @returns Object with provider and model\n * @throws Error if format is invalid\n */\nexport function parseModelString(modelString: string): LocaleModel | undefined {\n // Split on first colon only\n const [provider, name] = modelString.split(\":\", 2);\n\n if (!provider || !name) {\n return undefined;\n }\n\n return { provider, name };\n}\n\n/**\n * Validate and fetch all necessary API keys for the given configuration\n * This should be called once at initialization time\n *\n * @param config Model configuration (\"lingo.dev\" or locale-pair mapping)\n * @returns Validated API keys (provider ID -> API key)\n * @throws Error if required keys are missing\n */\nexport function validateAndGetApiKeys(\n config: \"lingo.dev\" | Record<string, string>,\n): ValidatedApiKeys {\n const keys: ValidatedApiKeys = {};\n const missingProviders: string[] = [];\n\n // Determine which providers are configured\n let providersToValidate: string[];\n\n if (config === \"lingo.dev\") {\n // Only need lingo.dev provider\n providersToValidate = [\"lingo.dev\"];\n } else {\n // Extract unique providers from model strings\n const providerSet = new Set<string>();\n Object.values(config).forEach((modelString) => {\n const model = parseModelString(modelString);\n if (model) {\n providerSet.add(model.provider);\n }\n });\n providersToValidate = Array.from(providerSet);\n }\n\n // Validate and fetch keys for each provider\n for (const provider of providersToValidate) {\n const providerConfig = providerDetails[provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Unknown provider \"${provider}\". Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Skip providers that don't require keys (like Ollama)\n if (!providerConfig.apiKeyEnvVar) {\n continue;\n }\n\n const key = getKeyFromEnv(providerConfig.apiKeyEnvVar);\n if (key) {\n keys[provider] = key;\n } else {\n missingProviders.push(provider);\n }\n }\n\n // If any keys are missing, throw with detailed error\n if (missingProviders.length > 0) {\n throw new Error(formatNoApiKeysError(missingProviders));\n }\n\n return keys;\n}\n\n/**\n * Create AI model instance from provider and model ID\n *\n * @param model Provider name (groq, google, openrouter, ollama, mistral) and model identifier\n * @param validatedKeys Pre-validated API keys from validateAndFetchApiKeys()\n * @returns LanguageModel instance\n * @throws Error if provider is not supported or API key is missing\n */\nexport function createAiModel(\n model: LocaleModel,\n validatedKeys: ValidatedApiKeys,\n): LanguageModel {\n const providerConfig = providerDetails[model.provider];\n\n if (!providerConfig) {\n throw new Error(\n `⚠️ Provider \"${model.provider}\" is not supported. Supported providers: ${Object.keys(providerDetails).join(\", \")}`,\n );\n }\n\n // Get API key if required\n const apiKey = providerConfig.apiKeyEnvVar\n ? validatedKeys[model.provider]\n : undefined;\n\n // TODO (AleksandrSl 25/12/2025): Do we really need to make a second check? Maybe creation should be combined with validation.\n // Verify key is present for providers that require it\n if (providerConfig.apiKeyEnvVar && !apiKey) {\n throw new Error(\n `⚠️ ${providerConfig.name} API key not found. Please set ${providerConfig.apiKeyEnvVar} environment variable.\\n\\n` +\n `This should not happen if validateAndFetchApiKeys() was called. Please restart the service.`,\n );\n }\n\n // Create the appropriate model instance\n switch (model.provider) {\n case \"groq\":\n return createGroq({ apiKey: apiKey! })(model.name);\n\n case \"google\":\n return createGoogleGenerativeAI({ apiKey: apiKey! })(model.name);\n\n case \"openrouter\":\n return createOpenRouter({ apiKey: apiKey! })(model.name);\n\n case \"ollama\":\n return ollama(model.name);\n\n case \"mistral\":\n return createMistral({ apiKey: apiKey! })(model.name);\n\n default:\n // This should be unreachable due to check above\n throw new Error(`⚠️ Provider \"${model.provider}\" is not implemented`);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+
2
+ //#region src/translators/lingo/provider-details.ts
3
+ const providerDetails = {
4
+ groq: {
5
+ name: "Groq",
6
+ apiKeyEnvVar: "GROQ_API_KEY",
7
+ apiKeyConfigKey: "llm.groqApiKey",
8
+ getKeyLink: "https://groq.com",
9
+ docsLink: "https://console.groq.com/docs/errors"
10
+ },
11
+ google: {
12
+ name: "Google",
13
+ apiKeyEnvVar: "GOOGLE_API_KEY",
14
+ apiKeyConfigKey: "llm.googleApiKey",
15
+ getKeyLink: "https://ai.google.dev/",
16
+ docsLink: "https://ai.google.dev/gemini-api/docs/troubleshooting"
17
+ },
18
+ openrouter: {
19
+ name: "OpenRouter",
20
+ apiKeyEnvVar: "OPENROUTER_API_KEY",
21
+ apiKeyConfigKey: "llm.openrouterApiKey",
22
+ getKeyLink: "https://openrouter.ai",
23
+ docsLink: "https://openrouter.ai/docs"
24
+ },
25
+ ollama: {
26
+ name: "Ollama",
27
+ apiKeyEnvVar: void 0,
28
+ apiKeyConfigKey: void 0,
29
+ getKeyLink: "https://ollama.com/download",
30
+ docsLink: "https://github.com/ollama/ollama/tree/main/docs"
31
+ },
32
+ mistral: {
33
+ name: "Mistral",
34
+ apiKeyEnvVar: "MISTRAL_API_KEY",
35
+ apiKeyConfigKey: "llm.mistralApiKey",
36
+ getKeyLink: "https://console.mistral.ai",
37
+ docsLink: "https://docs.mistral.ai"
38
+ },
39
+ "lingo.dev": {
40
+ name: "Lingo.dev",
41
+ apiKeyEnvVar: "LINGODOTDEV_API_KEY",
42
+ apiKeyConfigKey: "auth.apiKey",
43
+ getKeyLink: "https://lingo.dev",
44
+ docsLink: "https://lingo.dev/docs"
45
+ }
46
+ };
47
+ /**
48
+ * Format error message when API keys are missing for configured providers
49
+ * @param missingProviders List of providers that are missing API keys
50
+ * @param allProviders Optional: list of all configured providers for context
51
+ */
52
+ function formatNoApiKeysError(missingProviders, allProviders) {
53
+ const lines = [];
54
+ if (missingProviders.length === 0) return "Translation API keys validated successfully.";
55
+ if (allProviders && allProviders.length > missingProviders.length) lines.push(`Missing API keys for ${missingProviders.length} of ${allProviders.length} configured providers.`);
56
+ else lines.push(`Missing API keys for configured translation providers.`);
57
+ lines.push(`Missing API keys for:`);
58
+ for (const providerId of missingProviders) {
59
+ const details = providerDetails[providerId];
60
+ if (details) if (details.apiKeyEnvVar) lines.push(` • ${details.name}: ${details.apiKeyEnvVar} → ${details.getKeyLink}`);
61
+ else lines.push(` • ${details.name}: ${details.getKeyLink}`);
62
+ else lines.push(` • ${providerId}: (unknown provider)`);
63
+ }
64
+ lines.push(``, `👉 Set the required API keys:`, ` 1. Add to .env file (recommended)`, ` 2. Or export in terminal: export API_KEY_NAME=<your-key>`, ``);
65
+ return lines.join("\n");
66
+ }
67
+
68
+ //#endregion
69
+ exports.formatNoApiKeysError = formatNoApiKeysError;
@@ -0,0 +1,69 @@
1
+ //#region src/translators/lingo/provider-details.ts
2
+ const providerDetails = {
3
+ groq: {
4
+ name: "Groq",
5
+ apiKeyEnvVar: "GROQ_API_KEY",
6
+ apiKeyConfigKey: "llm.groqApiKey",
7
+ getKeyLink: "https://groq.com",
8
+ docsLink: "https://console.groq.com/docs/errors"
9
+ },
10
+ google: {
11
+ name: "Google",
12
+ apiKeyEnvVar: "GOOGLE_API_KEY",
13
+ apiKeyConfigKey: "llm.googleApiKey",
14
+ getKeyLink: "https://ai.google.dev/",
15
+ docsLink: "https://ai.google.dev/gemini-api/docs/troubleshooting"
16
+ },
17
+ openrouter: {
18
+ name: "OpenRouter",
19
+ apiKeyEnvVar: "OPENROUTER_API_KEY",
20
+ apiKeyConfigKey: "llm.openrouterApiKey",
21
+ getKeyLink: "https://openrouter.ai",
22
+ docsLink: "https://openrouter.ai/docs"
23
+ },
24
+ ollama: {
25
+ name: "Ollama",
26
+ apiKeyEnvVar: void 0,
27
+ apiKeyConfigKey: void 0,
28
+ getKeyLink: "https://ollama.com/download",
29
+ docsLink: "https://github.com/ollama/ollama/tree/main/docs"
30
+ },
31
+ mistral: {
32
+ name: "Mistral",
33
+ apiKeyEnvVar: "MISTRAL_API_KEY",
34
+ apiKeyConfigKey: "llm.mistralApiKey",
35
+ getKeyLink: "https://console.mistral.ai",
36
+ docsLink: "https://docs.mistral.ai"
37
+ },
38
+ "lingo.dev": {
39
+ name: "Lingo.dev",
40
+ apiKeyEnvVar: "LINGODOTDEV_API_KEY",
41
+ apiKeyConfigKey: "auth.apiKey",
42
+ getKeyLink: "https://lingo.dev",
43
+ docsLink: "https://lingo.dev/docs"
44
+ }
45
+ };
46
+ /**
47
+ * Format error message when API keys are missing for configured providers
48
+ * @param missingProviders List of providers that are missing API keys
49
+ * @param allProviders Optional: list of all configured providers for context
50
+ */
51
+ function formatNoApiKeysError(missingProviders, allProviders) {
52
+ const lines = [];
53
+ if (missingProviders.length === 0) return "Translation API keys validated successfully.";
54
+ if (allProviders && allProviders.length > missingProviders.length) lines.push(`Missing API keys for ${missingProviders.length} of ${allProviders.length} configured providers.`);
55
+ else lines.push(`Missing API keys for configured translation providers.`);
56
+ lines.push(`Missing API keys for:`);
57
+ for (const providerId of missingProviders) {
58
+ const details = providerDetails[providerId];
59
+ if (details) if (details.apiKeyEnvVar) lines.push(` • ${details.name}: ${details.apiKeyEnvVar} → ${details.getKeyLink}`);
60
+ else lines.push(` • ${details.name}: ${details.getKeyLink}`);
61
+ else lines.push(` • ${providerId}: (unknown provider)`);
62
+ }
63
+ lines.push(``, `👉 Set the required API keys:`, ` 1. Add to .env file (recommended)`, ` 2. Or export in terminal: export API_KEY_NAME=<your-key>`, ``);
64
+ return lines.join("\n");
65
+ }
66
+
67
+ //#endregion
68
+ export { formatNoApiKeysError };
69
+ //# sourceMappingURL=provider-details.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-details.mjs","names":["providerDetails: Record<string, ProviderDetails>","lines: string[]"],"sources":["../../../src/translators/lingo/provider-details.ts"],"sourcesContent":["/**\n * Provider details for error messages and documentation links\n */\n\nexport interface ProviderDetails {\n name: string; // Display name (e.g., \"Groq\", \"Google\")\n apiKeyEnvVar?: string; // Environment variable name (e.g., \"GROQ_API_KEY\")\n apiKeyConfigKey?: string; // Config key if applicable (e.g., \"llm.groqApiKey\")\n getKeyLink: string; // Link to get API key\n docsLink: string; // Link to API docs for troubleshooting\n}\n\nexport const providerDetails: Record<string, ProviderDetails> = {\n groq: {\n name: \"Groq\",\n apiKeyEnvVar: \"GROQ_API_KEY\",\n apiKeyConfigKey: \"llm.groqApiKey\",\n getKeyLink: \"https://groq.com\",\n docsLink: \"https://console.groq.com/docs/errors\",\n },\n google: {\n name: \"Google\",\n apiKeyEnvVar: \"GOOGLE_API_KEY\",\n apiKeyConfigKey: \"llm.googleApiKey\",\n getKeyLink: \"https://ai.google.dev/\",\n docsLink: \"https://ai.google.dev/gemini-api/docs/troubleshooting\",\n },\n openrouter: {\n name: \"OpenRouter\",\n apiKeyEnvVar: \"OPENROUTER_API_KEY\",\n apiKeyConfigKey: \"llm.openrouterApiKey\",\n getKeyLink: \"https://openrouter.ai\",\n docsLink: \"https://openrouter.ai/docs\",\n },\n ollama: {\n name: \"Ollama\",\n apiKeyEnvVar: undefined,\n apiKeyConfigKey: undefined,\n getKeyLink: \"https://ollama.com/download\",\n docsLink: \"https://github.com/ollama/ollama/tree/main/docs\",\n },\n mistral: {\n name: \"Mistral\",\n apiKeyEnvVar: \"MISTRAL_API_KEY\",\n apiKeyConfigKey: \"llm.mistralApiKey\",\n getKeyLink: \"https://console.mistral.ai\",\n docsLink: \"https://docs.mistral.ai\",\n },\n \"lingo.dev\": {\n name: \"Lingo.dev\",\n apiKeyEnvVar: \"LINGODOTDEV_API_KEY\",\n apiKeyConfigKey: \"auth.apiKey\",\n getKeyLink: \"https://lingo.dev\",\n docsLink: \"https://lingo.dev/docs\",\n },\n};\n\n/**\n * Format error message when API keys are missing for configured providers\n * @param missingProviders List of providers that are missing API keys\n * @param allProviders Optional: list of all configured providers for context\n */\nexport function formatNoApiKeysError(\n missingProviders: string[],\n allProviders?: string[],\n): string {\n const lines: string[] = [];\n\n if (missingProviders.length === 0) {\n // No missing providers (shouldn't happen, but handle it)\n return \"Translation API keys validated successfully.\";\n }\n\n // Header\n if (allProviders && allProviders.length > missingProviders.length) {\n lines.push(\n `Missing API keys for ${missingProviders.length} of ${allProviders.length} configured providers.`,\n );\n } else {\n lines.push(`Missing API keys for configured translation providers.`);\n }\n\n // List missing providers with their environment variables and links\n lines.push(`Missing API keys for:`);\n for (const providerId of missingProviders) {\n const details = providerDetails[providerId];\n if (details) {\n if (details.apiKeyEnvVar) {\n lines.push(\n ` • ${details.name}: ${details.apiKeyEnvVar} → ${details.getKeyLink}`,\n );\n } else {\n lines.push(` • ${details.name}: ${details.getKeyLink}`);\n }\n } else {\n lines.push(` • ${providerId}: (unknown provider)`);\n }\n }\n\n lines.push(\n ``,\n `👉 Set the required API keys:`,\n ` 1. Add to .env file (recommended)`,\n ` 2. Or export in terminal: export API_KEY_NAME=<your-key>`,\n ``,\n );\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";AAYA,MAAaA,kBAAmD;CAC9D,MAAM;EACJ,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,QAAQ;EACN,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,YAAY;EACV,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,QAAQ;EACN,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,SAAS;EACP,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACD,aAAa;EACX,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,YAAY;EACZ,UAAU;EACX;CACF;;;;;;AAOD,SAAgB,qBACd,kBACA,cACQ;CACR,MAAMC,QAAkB,EAAE;AAE1B,KAAI,iBAAiB,WAAW,EAE9B,QAAO;AAIT,KAAI,gBAAgB,aAAa,SAAS,iBAAiB,OACzD,OAAM,KACJ,wBAAwB,iBAAiB,OAAO,MAAM,aAAa,OAAO,wBAC3E;KAED,OAAM,KAAK,yDAAyD;AAItE,OAAM,KAAK,wBAAwB;AACnC,MAAK,MAAM,cAAc,kBAAkB;EACzC,MAAM,UAAU,gBAAgB;AAChC,MAAI,QACF,KAAI,QAAQ,aACV,OAAM,KACJ,QAAQ,QAAQ,KAAK,IAAI,QAAQ,aAAa,OAAO,QAAQ,aAC9D;MAED,OAAM,KAAK,QAAQ,QAAQ,KAAK,IAAI,QAAQ,aAAa;MAG3D,OAAM,KAAK,QAAQ,WAAW,sBAAsB;;AAIxD,OAAM,KACJ,IACA,iCACA,wCACA,+DACA,GACD;AAED,QAAO,MAAM,KAAK,KAAK"}
@@ -8,11 +8,11 @@ const require_timeout = require('../../utils/timeout.cjs');
8
8
  let ai = require("ai");
9
9
  let lingo_dev_sdk = require("lingo.dev/sdk");
10
10
 
11
- //#region src/translators/lingo/service.ts
11
+ //#region src/translators/lingo/translator.ts
12
12
  /**
13
13
  * Lingo translator using AI models
14
14
  */
15
- var Service = class {
15
+ var LingoTranslator = class {
16
16
  validatedKeys;
17
17
  constructor(config, logger) {
18
18
  this.config = config;
@@ -25,31 +25,29 @@ var Service = class {
25
25
  * Translate multiple entries
26
26
  */
27
27
  async translate(locale, entriesMap) {
28
- this.logger.debug(`[TRACE-LINGO] translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`);
28
+ this.logger.debug(`translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`);
29
29
  const sourceDictionary = require_api.dictionaryFrom(this.config.sourceLocale, Object.fromEntries(Object.entries(entriesMap).map(([hash, entry]) => [hash, entry.text])));
30
- this.logger.debug(`[TRACE-LINGO] Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
30
+ this.logger.debug(`Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
31
31
  return (await this.translateDictionary(sourceDictionary, locale)).entries || {};
32
32
  }
33
33
  /**
34
34
  * Translate a complete dictionary
35
35
  */
36
36
  async translateDictionary(sourceDictionary, targetLocale) {
37
- this.logger.debug(`[TRACE-LINGO] Chunking dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
38
37
  const chunks = this.chunkDictionary(sourceDictionary);
39
- this.logger.debug(`[TRACE-LINGO] Split into ${chunks.length} chunks`);
38
+ this.logger.debug(`Split dictionary with ${Object.keys(sourceDictionary.entries).length} into ${chunks.length} chunks`);
40
39
  const translatedChunks = [];
41
40
  for (let i = 0; i < chunks.length; i++) {
42
41
  const chunk = chunks[i];
43
- this.logger.debug(`[TRACE-LINGO] Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`);
42
+ this.logger.debug(`Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`);
44
43
  const chunkStartTime = performance.now();
45
44
  const translatedChunk = await this.translateChunk(chunk, targetLocale);
46
45
  const chunkEndTime = performance.now();
47
- this.logger.debug(`[TRACE-LINGO] Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`);
46
+ this.logger.debug(`Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`);
48
47
  translatedChunks.push(translatedChunk);
49
48
  }
50
- this.logger.debug(`[TRACE-LINGO] All chunks translated, merging results`);
51
49
  const result = this.mergeDictionaries(translatedChunks);
52
- this.logger.debug(`[TRACE-LINGO] Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`);
50
+ this.logger.debug(`Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`);
53
51
  return result;
54
52
  }
55
53
  /**
@@ -66,7 +64,7 @@ var Service = class {
66
64
  async translateWithLingoDotDev(sourceDictionary, targetLocale) {
67
65
  const apiKey = this.validatedKeys["lingo.dev"];
68
66
  if (!apiKey) throw new Error("Internal error: Lingo.dev API key not found after validation. Please restart the service.");
69
- this.logger.info(`Using Lingo.dev Engine to localize from "${this.config.sourceLocale}" to "${targetLocale}"`);
67
+ this.logger.debug(`Using Lingo.dev Engine to localize from "${this.config.sourceLocale}" to "${targetLocale}"`);
70
68
  const engine = new lingo_dev_sdk.LingoDotDevEngine({ apiKey });
71
69
  try {
72
70
  return await require_timeout.withTimeout(engine.localizeObject(sourceDictionary, {
@@ -85,7 +83,7 @@ var Service = class {
85
83
  async translateWithLLM(sourceDictionary, targetLocale) {
86
84
  const localeModel = require_model_factory.getLocaleModel(this.config.models, this.config.sourceLocale, targetLocale);
87
85
  if (!localeModel) throw new Error(`No model configured for translation from ${this.config.sourceLocale} to ${targetLocale}`);
88
- this.logger.info(`Using LLM ("${localeModel.provider}":"${localeModel.name}") to translate from "${this.config.sourceLocale}" to "${targetLocale}"`);
86
+ this.logger.debug(`Using LLM ("${localeModel.provider}":"${localeModel.name}") to translate from "${this.config.sourceLocale}" to "${targetLocale}"`);
89
87
  const aiModel = require_model_factory.createAiModel(localeModel, this.validatedKeys);
90
88
  try {
91
89
  return require_parse_xml.parseXmlFromResponseText((await require_timeout.withTimeout((0, ai.generateText)({
@@ -149,4 +147,4 @@ var Service = class {
149
147
  };
150
148
 
151
149
  //#endregion
152
- exports.Service = Service;
150
+ exports.LingoTranslator = LingoTranslator;
@@ -7,11 +7,11 @@ import { DEFAULT_TIMEOUTS, withTimeout } from "../../utils/timeout.mjs";
7
7
  import { generateText } from "ai";
8
8
  import { LingoDotDevEngine } from "lingo.dev/sdk";
9
9
 
10
- //#region src/translators/lingo/service.ts
10
+ //#region src/translators/lingo/translator.ts
11
11
  /**
12
12
  * Lingo translator using AI models
13
13
  */
14
- var Service = class {
14
+ var LingoTranslator = class {
15
15
  validatedKeys;
16
16
  constructor(config, logger) {
17
17
  this.config = config;
@@ -24,31 +24,29 @@ var Service = class {
24
24
  * Translate multiple entries
25
25
  */
26
26
  async translate(locale, entriesMap) {
27
- this.logger.debug(`[TRACE-LINGO] translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`);
27
+ this.logger.debug(`translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`);
28
28
  const sourceDictionary = dictionaryFrom(this.config.sourceLocale, Object.fromEntries(Object.entries(entriesMap).map(([hash, entry]) => [hash, entry.text])));
29
- this.logger.debug(`[TRACE-LINGO] Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
29
+ this.logger.debug(`Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
30
30
  return (await this.translateDictionary(sourceDictionary, locale)).entries || {};
31
31
  }
32
32
  /**
33
33
  * Translate a complete dictionary
34
34
  */
35
35
  async translateDictionary(sourceDictionary, targetLocale) {
36
- this.logger.debug(`[TRACE-LINGO] Chunking dictionary with ${Object.keys(sourceDictionary.entries).length} entries`);
37
36
  const chunks = this.chunkDictionary(sourceDictionary);
38
- this.logger.debug(`[TRACE-LINGO] Split into ${chunks.length} chunks`);
37
+ this.logger.debug(`Split dictionary with ${Object.keys(sourceDictionary.entries).length} into ${chunks.length} chunks`);
39
38
  const translatedChunks = [];
40
39
  for (let i = 0; i < chunks.length; i++) {
41
40
  const chunk = chunks[i];
42
- this.logger.debug(`[TRACE-LINGO] Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`);
41
+ this.logger.debug(`Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`);
43
42
  const chunkStartTime = performance.now();
44
43
  const translatedChunk = await this.translateChunk(chunk, targetLocale);
45
44
  const chunkEndTime = performance.now();
46
- this.logger.debug(`[TRACE-LINGO] Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`);
45
+ this.logger.debug(`Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`);
47
46
  translatedChunks.push(translatedChunk);
48
47
  }
49
- this.logger.debug(`[TRACE-LINGO] All chunks translated, merging results`);
50
48
  const result = this.mergeDictionaries(translatedChunks);
51
- this.logger.debug(`[TRACE-LINGO] Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`);
49
+ this.logger.debug(`Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`);
52
50
  return result;
53
51
  }
54
52
  /**
@@ -65,7 +63,7 @@ var Service = class {
65
63
  async translateWithLingoDotDev(sourceDictionary, targetLocale) {
66
64
  const apiKey = this.validatedKeys["lingo.dev"];
67
65
  if (!apiKey) throw new Error("Internal error: Lingo.dev API key not found after validation. Please restart the service.");
68
- this.logger.info(`Using Lingo.dev Engine to localize from "${this.config.sourceLocale}" to "${targetLocale}"`);
66
+ this.logger.debug(`Using Lingo.dev Engine to localize from "${this.config.sourceLocale}" to "${targetLocale}"`);
69
67
  const engine = new LingoDotDevEngine({ apiKey });
70
68
  try {
71
69
  return await withTimeout(engine.localizeObject(sourceDictionary, {
@@ -84,7 +82,7 @@ var Service = class {
84
82
  async translateWithLLM(sourceDictionary, targetLocale) {
85
83
  const localeModel = getLocaleModel(this.config.models, this.config.sourceLocale, targetLocale);
86
84
  if (!localeModel) throw new Error(`No model configured for translation from ${this.config.sourceLocale} to ${targetLocale}`);
87
- this.logger.info(`Using LLM ("${localeModel.provider}":"${localeModel.name}") to translate from "${this.config.sourceLocale}" to "${targetLocale}"`);
85
+ this.logger.debug(`Using LLM ("${localeModel.provider}":"${localeModel.name}") to translate from "${this.config.sourceLocale}" to "${targetLocale}"`);
88
86
  const aiModel = createAiModel(localeModel, this.validatedKeys);
89
87
  try {
90
88
  return parseXmlFromResponseText((await withTimeout(generateText({
@@ -148,5 +146,5 @@ var Service = class {
148
146
  };
149
147
 
150
148
  //#endregion
151
- export { Service };
152
- //# sourceMappingURL=service.mjs.map
149
+ export { LingoTranslator };
150
+ //# sourceMappingURL=translator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translator.mjs","names":["config: LingoTranslatorConfig","logger: Logger","sourceDictionary: DictionarySchema","translatedChunks: DictionarySchema[]","chunks: DictionarySchema[]","mergedEntries: Record<string, string>"],"sources":["../../../src/translators/lingo/translator.ts"],"sourcesContent":["import { generateText } from \"ai\";\nimport { LingoDotDevEngine } from \"lingo.dev/sdk\";\nimport { dictionaryFrom, type DictionarySchema, type TranslatableEntry, type Translator, } from \"../api\";\nimport { getSystemPrompt } from \"./prompt\";\nimport { obj2xml, parseXmlFromResponseText } from \"../parse-xml\";\nimport { shots } from \"./shots\";\nimport { createAiModel, getLocaleModel, validateAndGetApiKeys, type ValidatedApiKeys, } from \"./model-factory\";\nimport { Logger } from \"../../utils/logger\";\nimport { DEFAULT_TIMEOUTS, withTimeout } from \"../../utils/timeout\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\n/**\n * Lingo Translator configuration\n */\nexport interface LingoTranslatorConfig {\n models: \"lingo.dev\" | Record<string, string>;\n sourceLocale: LocaleCode;\n prompt?: string;\n}\n\n/**\n * Lingo translator using AI models\n */\nexport class LingoTranslator implements Translator<LingoTranslatorConfig> {\n private readonly validatedKeys: ValidatedApiKeys;\n\n constructor(\n readonly config: LingoTranslatorConfig,\n private logger: Logger,\n ) {\n this.logger.info(\"Validating API keys for translation...\");\n this.validatedKeys = validateAndGetApiKeys(config.models);\n this.logger.info(\"✅ API keys validated successfully\");\n }\n\n /**\n * Translate multiple entries\n */\n async translate(\n locale: LocaleCode,\n entriesMap: Record<string, TranslatableEntry>,\n ): Promise<Record<string, string>> {\n this.logger.debug(\n `translate() called for ${locale} with ${Object.keys(entriesMap).length} entries`,\n );\n\n const sourceDictionary: DictionarySchema = dictionaryFrom(\n this.config.sourceLocale,\n Object.fromEntries(\n Object.entries(entriesMap).map(([hash, entry]) => [hash, entry.text]),\n ),\n );\n\n this.logger.debug(\n `Created source dictionary with ${Object.keys(sourceDictionary.entries).length} entries`,\n );\n const translated = await this.translateDictionary(sourceDictionary, locale);\n\n return translated.entries || {};\n }\n\n /**\n * Translate a complete dictionary\n */\n private async translateDictionary(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n const chunks = this.chunkDictionary(sourceDictionary);\n this.logger.debug(\n `Split dictionary with ${Object.keys(sourceDictionary.entries).length} into ${chunks.length} chunks`,\n );\n\n const translatedChunks: DictionarySchema[] = [];\n\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n this.logger.debug(\n `Translating chunk ${i + 1}/${chunks.length} with ${Object.keys(chunk.entries).length} entries`,\n );\n const chunkStartTime = performance.now();\n\n const translatedChunk = await this.translateChunk(chunk, targetLocale);\n\n const chunkEndTime = performance.now();\n this.logger.debug(\n `Chunk ${i + 1}/${chunks.length} completed in ${(chunkEndTime - chunkStartTime).toFixed(2)}ms`,\n );\n\n translatedChunks.push(translatedChunk);\n }\n\n const result = this.mergeDictionaries(translatedChunks);\n this.logger.debug(\n `Merge completed, final dictionary has ${Object.keys(result.entries).length} entries`,\n );\n\n return result;\n }\n\n /**\n * Translate a single chunk\n */\n private async translateChunk(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n if (this.config.models === \"lingo.dev\") {\n return this.translateWithLingoDotDev(sourceDictionary, targetLocale);\n } else {\n return this.translateWithLLM(sourceDictionary, targetLocale);\n }\n }\n\n /**\n * Translate using Lingo.dev Engine\n * Times out after 60 seconds to prevent indefinite hangs\n */\n private async translateWithLingoDotDev(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n const apiKey = this.validatedKeys[\"lingo.dev\"];\n if (!apiKey) {\n throw new Error(\n \"Internal error: Lingo.dev API key not found after validation. Please restart the service.\",\n );\n }\n\n this.logger.debug(\n `Using Lingo.dev Engine to localize from \"${this.config.sourceLocale}\" to \"${targetLocale}\"`,\n );\n\n const engine = new LingoDotDevEngine({ apiKey });\n\n try {\n const result = await withTimeout(\n engine.localizeObject(sourceDictionary, {\n sourceLocale: this.config.sourceLocale,\n targetLocale: targetLocale,\n }),\n DEFAULT_TIMEOUTS.AI_API,\n `Lingo.dev API translation to ${targetLocale}`,\n );\n\n return result as DictionarySchema;\n } catch (error) {\n this.logger.error(`translateWithLingoDotDev() failed:`, error);\n throw new Error(\n `Lingo.dev translation failed: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n }\n\n /**\n * Translate using generic LLM\n * Times out after 60 seconds to prevent indefinite hangs\n */\n private async translateWithLLM(\n sourceDictionary: DictionarySchema,\n targetLocale: string,\n ): Promise<DictionarySchema> {\n const localeModel = getLocaleModel(\n this.config.models as Record<string, string>,\n this.config.sourceLocale,\n targetLocale,\n );\n\n if (!localeModel) {\n throw new Error(\n `No model configured for translation from ${this.config.sourceLocale} to ${targetLocale}`,\n );\n }\n\n this.logger.debug(\n `Using LLM (\"${localeModel.provider}\":\"${localeModel.name}\") to translate from \"${this.config.sourceLocale}\" to \"${targetLocale}\"`,\n );\n\n const aiModel = createAiModel(localeModel, this.validatedKeys);\n\n try {\n const response = await withTimeout(\n generateText({\n model: aiModel,\n messages: [\n {\n role: \"system\",\n content: getSystemPrompt({\n sourceLocale: this.config.sourceLocale,\n targetLocale,\n prompt: this.config.prompt,\n }),\n },\n // Add few-shot examples\n ...shots.flatMap((shotsTuple) => [\n {\n role: \"user\" as const,\n content: obj2xml(shotsTuple[0]),\n },\n {\n role: \"assistant\" as const,\n content: obj2xml(shotsTuple[1]),\n },\n ]),\n {\n role: \"user\",\n content: obj2xml(sourceDictionary),\n },\n ],\n }),\n DEFAULT_TIMEOUTS.AI_API,\n `${localeModel.provider} LLM translation to ${targetLocale}`,\n );\n\n return parseXmlFromResponseText<DictionarySchema>(response.text);\n } catch (error) {\n throw new Error(\n `LLM translation failed with ${localeModel.provider}: ${error instanceof Error ? error.message : \"Unknown error\"}`,\n );\n }\n }\n\n /**\n * Split dictionary into chunks for processing\n */\n private chunkDictionary(dictionary: DictionarySchema): DictionarySchema[] {\n const MAX_ENTRIES_PER_CHUNK = 100;\n const { entries, ...rest } = dictionary;\n const chunks: DictionarySchema[] = [];\n\n const entryPairs = Object.entries(entries);\n\n // Split entries into chunks of MAX_ENTRIES_PER_CHUNK\n for (let i = 0; i < entryPairs.length; i += MAX_ENTRIES_PER_CHUNK) {\n const chunkEntries = entryPairs.slice(i, i + MAX_ENTRIES_PER_CHUNK);\n chunks.push({\n ...rest,\n entries: Object.fromEntries(chunkEntries),\n });\n }\n\n return chunks;\n }\n\n /**\n * Merge multiple dictionaries into one\n */\n private mergeDictionaries(\n dictionaries: DictionarySchema[],\n ): DictionarySchema {\n if (dictionaries.length === 0) {\n return dictionaryFrom(this.config.sourceLocale, {});\n }\n\n // Merge all entries from all dictionaries\n const mergedEntries: Record<string, string> = {};\n for (const dict of dictionaries) {\n Object.assign(mergedEntries, dict.entries);\n }\n\n return {\n version: dictionaries[0].version,\n locale: dictionaries[0].locale,\n entries: mergedEntries,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+
2
+ //#region src/translators/memory-cache.ts
3
+ /**
4
+ * In memory translation cache implementation
5
+ */
6
+ var MemoryTranslationCache = class {
7
+ cache = /* @__PURE__ */ new Map();
8
+ constructor() {}
9
+ async get(locale, hashes) {
10
+ const localeCache = this.cache.get(locale);
11
+ if (!localeCache) return {};
12
+ if (hashes) return hashes.reduce((acc, hash) => ({
13
+ ...acc,
14
+ [hash]: localeCache.get(hash)
15
+ }), {});
16
+ return Object.fromEntries(localeCache);
17
+ }
18
+ /**
19
+ * Update cache with new translations (merge)
20
+ */
21
+ async update(locale, translations) {
22
+ let localeCache = this.cache.get(locale);
23
+ if (!localeCache) {
24
+ localeCache = /* @__PURE__ */ new Map();
25
+ this.cache.set(locale, localeCache);
26
+ }
27
+ for (const [key, value] of Object.entries(translations)) localeCache.set(key, value);
28
+ }
29
+ /**
30
+ * Replace entire cache for a locale
31
+ */
32
+ async set(locale, translations) {
33
+ this.cache.set(locale, new Map(Object.entries(translations)));
34
+ }
35
+ async has(locale) {
36
+ return this.cache.has(locale);
37
+ }
38
+ async clear(locale) {
39
+ this.cache.delete(locale);
40
+ }
41
+ async clearAll() {
42
+ this.cache.clear();
43
+ }
44
+ };
45
+
46
+ //#endregion
47
+ exports.MemoryTranslationCache = MemoryTranslationCache;
@@ -0,0 +1,47 @@
1
+ //#region src/translators/memory-cache.ts
2
+ /**
3
+ * In memory translation cache implementation
4
+ */
5
+ var MemoryTranslationCache = class {
6
+ cache = /* @__PURE__ */ new Map();
7
+ constructor() {}
8
+ async get(locale, hashes) {
9
+ const localeCache = this.cache.get(locale);
10
+ if (!localeCache) return {};
11
+ if (hashes) return hashes.reduce((acc, hash) => ({
12
+ ...acc,
13
+ [hash]: localeCache.get(hash)
14
+ }), {});
15
+ return Object.fromEntries(localeCache);
16
+ }
17
+ /**
18
+ * Update cache with new translations (merge)
19
+ */
20
+ async update(locale, translations) {
21
+ let localeCache = this.cache.get(locale);
22
+ if (!localeCache) {
23
+ localeCache = /* @__PURE__ */ new Map();
24
+ this.cache.set(locale, localeCache);
25
+ }
26
+ for (const [key, value] of Object.entries(translations)) localeCache.set(key, value);
27
+ }
28
+ /**
29
+ * Replace entire cache for a locale
30
+ */
31
+ async set(locale, translations) {
32
+ this.cache.set(locale, new Map(Object.entries(translations)));
33
+ }
34
+ async has(locale) {
35
+ return this.cache.has(locale);
36
+ }
37
+ async clear(locale) {
38
+ this.cache.delete(locale);
39
+ }
40
+ async clearAll() {
41
+ this.cache.clear();
42
+ }
43
+ };
44
+
45
+ //#endregion
46
+ export { MemoryTranslationCache };
47
+ //# sourceMappingURL=memory-cache.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-cache.mjs","names":[],"sources":["../../src/translators/memory-cache.ts"],"sourcesContent":["import type { TranslationCache } from \"./cache\";\nimport type { LocaleCode } from \"lingo.dev/spec\";\n\n/**\n * In memory translation cache implementation\n */\nexport class MemoryTranslationCache implements TranslationCache {\n private cache: Map<LocaleCode, Map<string, string>> = new Map();\n\n constructor() {}\n\n async get(\n locale: LocaleCode,\n hashes?: string[],\n ): Promise<Record<string, string>> {\n const localeCache = this.cache.get(locale);\n if (!localeCache) {\n return {};\n }\n if (hashes) {\n return hashes.reduce(\n (acc, hash) => ({ ...acc, [hash]: localeCache.get(hash) }),\n {},\n );\n }\n return Object.fromEntries(localeCache);\n }\n\n /**\n * Update cache with new translations (merge)\n */\n async update(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n let localeCache = this.cache.get(locale);\n if (!localeCache) {\n localeCache = new Map();\n this.cache.set(locale, localeCache);\n }\n for (const [key, value] of Object.entries(translations)) {\n localeCache.set(key, value);\n }\n }\n\n /**\n * Replace entire cache for a locale\n */\n async set(\n locale: LocaleCode,\n translations: Record<string, string>,\n ): Promise<void> {\n this.cache.set(locale, new Map(Object.entries(translations)));\n }\n\n async has(locale: LocaleCode): Promise<boolean> {\n return this.cache.has(locale);\n }\n\n async clear(locale: LocaleCode): Promise<void> {\n this.cache.delete(locale);\n }\n\n async clearAll(): Promise<void> {\n this.cache.clear();\n }\n}\n"],"mappings":";;;;AAMA,IAAa,yBAAb,MAAgE;CAC9D,AAAQ,wBAA8C,IAAI,KAAK;CAE/D,cAAc;CAEd,MAAM,IACJ,QACA,QACiC;EACjC,MAAM,cAAc,KAAK,MAAM,IAAI,OAAO;AAC1C,MAAI,CAAC,YACH,QAAO,EAAE;AAEX,MAAI,OACF,QAAO,OAAO,QACX,KAAK,UAAU;GAAE,GAAG;IAAM,OAAO,YAAY,IAAI,KAAK;GAAE,GACzD,EAAE,CACH;AAEH,SAAO,OAAO,YAAY,YAAY;;;;;CAMxC,MAAM,OACJ,QACA,cACe;EACf,IAAI,cAAc,KAAK,MAAM,IAAI,OAAO;AACxC,MAAI,CAAC,aAAa;AAChB,iCAAc,IAAI,KAAK;AACvB,QAAK,MAAM,IAAI,QAAQ,YAAY;;AAErC,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,aAAa,CACrD,aAAY,IAAI,KAAK,MAAM;;;;;CAO/B,MAAM,IACJ,QACA,cACe;AACf,OAAK,MAAM,IAAI,QAAQ,IAAI,IAAI,OAAO,QAAQ,aAAa,CAAC,CAAC;;CAG/D,MAAM,IAAI,QAAsC;AAC9C,SAAO,KAAK,MAAM,IAAI,OAAO;;CAG/B,MAAM,MAAM,QAAmC;AAC7C,OAAK,MAAM,OAAO,OAAO;;CAG3B,MAAM,WAA0B;AAC9B,OAAK,MAAM,OAAO"}
@@ -14,23 +14,17 @@ let ai = require("ai");
14
14
  */
15
15
  var PluralizationService = class {
16
16
  languageModel;
17
- modelName;
18
17
  cache = /* @__PURE__ */ new Map();
19
18
  prompt;
20
19
  sourceLocale;
21
20
  constructor(config, logger) {
22
21
  this.logger = logger;
23
22
  const localeModel = require_model_factory.parseModelString(config.model);
24
- if (!localeModel) throw new Error(`Invalid model format: "${config.model}"`);
25
- const modelsConfig = { "*:*": config.model };
26
- this.logger.info("Validating API keys for pluralization...");
27
- const validatedKeys = require_model_factory.validateAndGetApiKeys(modelsConfig);
28
- this.logger.info("✅ API keys validated for pluralization");
29
- this.languageModel = require_model_factory.createAiModel(localeModel, validatedKeys);
30
- this.modelName = `${localeModel.provider}:${localeModel.name}`;
23
+ if (!localeModel) throw new Error(`Invalid model format in pluralization service: "${config.model}"`);
24
+ this.languageModel = require_model_factory.createAiModel(localeModel, require_model_factory.validateAndGetApiKeys({ "*:*": config.model }));
31
25
  this.sourceLocale = config.sourceLocale;
32
26
  this.prompt = require_prompt.getSystemPrompt({ sourceLocale: config.sourceLocale });
33
- this.logger.info(`Initialized pluralization service with ${this.modelName}`);
27
+ this.logger.debug(`Initialized pluralization service with ${localeModel.provider}:${localeModel.name}`);
34
28
  }
35
29
  /**
36
30
  * Generate ICU formats for multiple candidates in a single batch
@@ -40,23 +34,20 @@ var PluralizationService = class {
40
34
  * @returns Map of hash -> ICU generation result
41
35
  */
42
36
  async generateBatch(candidates, batchSize = 10) {
43
- const results = /* @__PURE__ */ new Map();
44
- const uncachedCandidates = candidates.filter((c) => {
37
+ const { uncachedCandidates, results } = candidates.reduce((acc, c) => {
45
38
  const cached = this.cache.get(c.hash);
46
- if (cached) {
47
- results.set(c.hash, cached);
48
- return false;
49
- }
50
- return true;
39
+ if (cached) acc.results.set(c.hash, cached);
40
+ else acc.uncachedCandidates.push(c);
41
+ return acc;
42
+ }, {
43
+ uncachedCandidates: [],
44
+ results: /* @__PURE__ */ new Map()
51
45
  });
52
- if (uncachedCandidates.length === 0) {
53
- this.logger.debug(`All ${candidates.length} candidates found in cache, skipping LLM call`);
54
- return results;
55
- }
56
- this.logger.info(`Processing ${uncachedCandidates.length} candidates (${candidates.length - uncachedCandidates.length} cached)`);
46
+ if (uncachedCandidates.length === 0) return results;
47
+ this.logger.debug(`Processing ${uncachedCandidates.length} candidates (${candidates.length - uncachedCandidates.length} cached)`);
57
48
  for (let i = 0; i < uncachedCandidates.length; i += batchSize) {
58
49
  const batch = uncachedCandidates.slice(i, i + batchSize);
59
- this.logger.info(`Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(uncachedCandidates.length / batchSize)} (${batch.length} candidates)`);
50
+ this.logger.debug(`Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(uncachedCandidates.length / batchSize)} (${batch.length} candidates)`);
60
51
  const batchResults = await this.processBatch(batch);
61
52
  for (const [hash, result] of batchResults) {
62
53
  results.set(hash, result);
@@ -98,7 +89,7 @@ var PluralizationService = class {
98
89
  content: require_parse_xml.obj2xml(batchRequest)
99
90
  }
100
91
  ]
101
- }), require_timeout.DEFAULT_TIMEOUTS.AI_API * 2, `Pluralization with ${this.modelName}`)).text.trim();
92
+ }), require_timeout.DEFAULT_TIMEOUTS.AI_API * 2, `Pluralization with ${this.languageModel}`)).text.trim();
102
93
  this.logger.debug(`LLM XML response: ${responseText.substring(0, 200)}...`);
103
94
  const parsed = require_parse_xml.parseXmlFromResponseText(responseText);
104
95
  const resultArray = Array.isArray(parsed.results.result) ? parsed.results.result : [parsed.results.result];
@@ -124,7 +115,7 @@ var PluralizationService = class {
124
115
  }
125
116
  }
126
117
  for (const candidate of candidates) if (!results.has(candidate.hash)) {
127
- this.logger.warn(`No result returned for candidate: ${candidate.sourceText}`);
118
+ this.logger.warn(`No result returned for a candidate: ${candidate.sourceText}`);
128
119
  results.set(candidate.hash, {
129
120
  success: false,
130
121
  error: "No result returned by LLM"
@@ -164,9 +155,9 @@ var PluralizationService = class {
164
155
  failed: 0,
165
156
  durationMs: 0
166
157
  };
167
- this.logger.info(`Starting pluralization processing for ${totalEntries} entries`);
158
+ this.logger.debug(`Starting pluralization processing for ${totalEntries} entries`);
168
159
  const candidates = require_pattern_detector.detectPluralCandidates(Object.fromEntries(Object.entries(metadata.entries).map(([hash, entry]) => [hash, entry.sourceText])), this.logger);
169
- this.logger.info(`Found ${candidates.length} plural candidates (${(candidates.length / totalEntries * 100).toFixed(1)}%)`);
160
+ this.logger.debug(`Found ${candidates.length} plural candidates (${(candidates.length / totalEntries * 100).toFixed(1)}%)`);
170
161
  if (candidates.length === 0) return {
171
162
  total: totalEntries,
172
163
  candidates: 0,
@@ -210,12 +201,12 @@ var PluralizationService = class {
210
201
  failed++;
211
202
  continue;
212
203
  }
213
- this.logger.info(`Pluralizing: "${entry.sourceText}" -> "${result.icuText}"`);
204
+ this.logger.debug(`Pluralizing: "${entry.sourceText}" -> "${result.icuText}"`);
214
205
  entry.sourceText = result.icuText;
215
206
  pluralized++;
216
207
  }
217
208
  const duration = performance.now() - startTime;
218
- this.logger.info(`Pluralization completed: ${pluralized} pluralized, ${rejected} rejected, ${failed} failed in ${duration.toFixed(0)}ms`);
209
+ this.logger.debug(`Pluralization completed: ${pluralized} pluralized, ${rejected} rejected, ${failed} failed in ${duration.toFixed(0)}ms`);
219
210
  return {
220
211
  total: totalEntries,
221
212
  candidates: candidates.length,
@@ -225,22 +216,6 @@ var PluralizationService = class {
225
216
  durationMs: duration
226
217
  };
227
218
  }
228
- /**
229
- * Clear the cache
230
- */
231
- clearCache() {
232
- this.cache.clear();
233
- this.logger.debug("Pluralization cache cleared");
234
- }
235
- /**
236
- * Get cache statistics
237
- */
238
- getCacheStats() {
239
- return {
240
- size: this.cache.size,
241
- hits: 0
242
- };
243
- }
244
219
  };
245
220
 
246
221
  //#endregion