ai-i18n-tools 1.8.8 → 1.8.10

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 (92) hide show
  1. package/bin/ai-i18n-tools +8 -0
  2. package/dist/api/llm-client.d.ts +43 -26
  3. package/dist/api/llm-client.d.ts.map +1 -1
  4. package/dist/api/llm-client.js +219 -17
  5. package/dist/api/llm-client.js.map +1 -1
  6. package/dist/build-info.generated.d.ts +1 -1
  7. package/dist/build-info.generated.js +1 -1
  8. package/dist/cli/doc-shell-ts-translate.d.ts.map +1 -1
  9. package/dist/cli/doc-shell-ts-translate.js +5 -3
  10. package/dist/cli/doc-shell-ts-translate.js.map +1 -1
  11. package/dist/cli/doc-translate.d.ts +1 -0
  12. package/dist/cli/doc-translate.d.ts.map +1 -1
  13. package/dist/cli/doc-translate.js +34 -77
  14. package/dist/cli/doc-translate.js.map +1 -1
  15. package/dist/cli/fumadocs-meta-translate.d.ts.map +1 -1
  16. package/dist/cli/fumadocs-meta-translate.js +5 -3
  17. package/dist/cli/fumadocs-meta-translate.js.map +1 -1
  18. package/dist/cli/fumadocs-ui-catalog.d.ts.map +1 -1
  19. package/dist/cli/fumadocs-ui-catalog.js +5 -4
  20. package/dist/cli/fumadocs-ui-catalog.js.map +1 -1
  21. package/dist/cli/index.js +9 -6
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/cli/translate-json-run.d.ts.map +1 -1
  24. package/dist/cli/translate-json-run.js +9 -5
  25. package/dist/cli/translate-json-run.js.map +1 -1
  26. package/dist/cli/translate-svg.d.ts.map +1 -1
  27. package/dist/cli/translate-svg.js +4 -1
  28. package/dist/cli/translate-svg.js.map +1 -1
  29. package/dist/cli/translate-ui-strings.d.ts +17 -0
  30. package/dist/cli/translate-ui-strings.d.ts.map +1 -1
  31. package/dist/cli/translate-ui-strings.js +106 -33
  32. package/dist/cli/translate-ui-strings.js.map +1 -1
  33. package/dist/cli/translation-failure-log.d.ts +3 -0
  34. package/dist/cli/translation-failure-log.d.ts.map +1 -0
  35. package/dist/cli/translation-failure-log.js +2 -0
  36. package/dist/cli/translation-failure-log.js.map +1 -0
  37. package/dist/cli/vitepress-theme-catalog.d.ts.map +1 -1
  38. package/dist/cli/vitepress-theme-catalog.js +5 -4
  39. package/dist/cli/vitepress-theme-catalog.js.map +1 -1
  40. package/dist/cli/write-heading-ids.d.ts.map +1 -1
  41. package/dist/cli/write-heading-ids.js +4 -0
  42. package/dist/cli/write-heading-ids.js.map +1 -1
  43. package/dist/core/config.d.ts +2 -2
  44. package/dist/core/config.d.ts.map +1 -1
  45. package/dist/core/config.js +4 -2
  46. package/dist/core/config.js.map +1 -1
  47. package/dist/core/locale-utils.d.ts +48 -11
  48. package/dist/core/locale-utils.d.ts.map +1 -1
  49. package/dist/core/locale-utils.js +303 -23
  50. package/dist/core/locale-utils.js.map +1 -1
  51. package/dist/core/prompt-builder.d.ts +2 -0
  52. package/dist/core/prompt-builder.d.ts.map +1 -1
  53. package/dist/core/prompt-builder.js +10 -5
  54. package/dist/core/prompt-builder.js.map +1 -1
  55. package/dist/core/prompts.d.ts +2 -2
  56. package/dist/core/prompts.d.ts.map +1 -1
  57. package/dist/core/prompts.js +3 -3
  58. package/dist/core/prompts.js.map +1 -1
  59. package/dist/core/types.d.ts +2 -0
  60. package/dist/core/types.d.ts.map +1 -1
  61. package/dist/core/types.js +4 -0
  62. package/dist/core/types.js.map +1 -1
  63. package/dist/extractors/svg-extractor.d.ts.map +1 -1
  64. package/dist/extractors/svg-extractor.js +73 -72
  65. package/dist/extractors/svg-extractor.js.map +1 -1
  66. package/dist/i18n/locales/de.json +4 -4
  67. package/dist/i18n/locales/es.json +4 -4
  68. package/dist/i18n/locales/fr.json +4 -4
  69. package/dist/i18n/locales/hi.json +4 -4
  70. package/dist/i18n/locales/ja.json +4 -4
  71. package/dist/i18n/locales/ko.json +4 -4
  72. package/dist/i18n/locales/pt-BR.json +4 -4
  73. package/dist/i18n/locales/zh-Hans.json +4 -4
  74. package/dist/i18n/locales/zh-Hant.json +4 -4
  75. package/dist/index.d.ts +1 -1
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +1 -1
  78. package/dist/index.js.map +1 -1
  79. package/dist/markdown/write-heading-ids-core.d.ts +15 -5
  80. package/dist/markdown/write-heading-ids-core.d.ts.map +1 -1
  81. package/dist/markdown/write-heading-ids-core.js +100 -10
  82. package/dist/markdown/write-heading-ids-core.js.map +1 -1
  83. package/dist/processors/placeholder-integrity.d.ts +7 -8
  84. package/dist/processors/placeholder-integrity.d.ts.map +1 -1
  85. package/dist/processors/placeholder-integrity.js +56 -40
  86. package/dist/processors/placeholder-integrity.js.map +1 -1
  87. package/dist/utils/translation-failure-log.d.ts +42 -0
  88. package/dist/utils/translation-failure-log.d.ts.map +1 -0
  89. package/dist/utils/translation-failure-log.js +95 -0
  90. package/dist/utils/translation-failure-log.js.map +1 -0
  91. package/docs/ai-i18n-tools-context.md +1 -0
  92. package/package.json +11 -10
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PATH-friendly name (no .mjs suffix) beside the published package.json bin
4
+ * (`ai-i18n-tools.mjs`). With direnv `PATH_add bin` in `.envrc`, the bare
5
+ * `ai-i18n-tools` command works in this checkout. pnpm still does not link the
6
+ * root package's own bin into `node_modules/.bin`.
7
+ */
8
+ import "./ai-i18n-tools.mjs";
@@ -2,32 +2,27 @@ import type { CldrPluralForm, I18nConfig } from "../core/types.js";
2
2
  import { type BatchTranslationResult, type ChatResponse, type LlmUsageStats, type Segment, type TranslationResult } from "../core/types.js";
3
3
  import { type DocumentBatchResponseFormat, type DocumentPromptContentType, type ProofreadUISlotResult } from "../core/prompt-builder.js";
4
4
  import type { Logger } from "../utils/logger.js";
5
+ /** Details attached when every model in a fallback chain fails. */
6
+ export interface LlmAllModelsFailedDetails {
7
+ systemPrompt: string;
8
+ userContent: string;
9
+ lastModel: string;
10
+ lastError: unknown;
11
+ /** HTTP response body text when the model returned content but parsing/validation failed. */
12
+ lastRawAssistantContent?: string;
13
+ /** Tokens spent across billed-but-discarded attempts before every model failed. */
14
+ wastedUsage?: LlmUsageStats;
15
+ /** USD cost spent across billed-but-discarded attempts (undefined when no cost was reported). */
16
+ wastedCost?: number;
17
+ }
18
+ /** Thrown when every model in the chain fails for a translation request. */
19
+ export declare class LlmAllModelsFailedError extends Error {
20
+ readonly details: LlmAllModelsFailedDetails;
21
+ constructor(message: string, details: LlmAllModelsFailedDetails);
22
+ }
5
23
  /** Thrown when every model in the chain fails for {@link LlmClient.translateDocumentBatch}. */
6
- export declare class DocumentBatchAllModelsFailedError extends Error {
7
- readonly details: {
8
- systemPrompt: string;
9
- userContent: string;
10
- lastModel: string;
11
- lastError: unknown;
12
- /** HTTP response body text when the model returned content but parsing failed. */
13
- lastRawAssistantContent?: string;
14
- /** Tokens spent across billed-but-discarded attempts before every model failed. */
15
- wastedUsage?: LlmUsageStats;
16
- /** USD cost spent across billed-but-discarded attempts (undefined when no cost was reported). */
17
- wastedCost?: number;
18
- };
19
- constructor(message: string, details: {
20
- systemPrompt: string;
21
- userContent: string;
22
- lastModel: string;
23
- lastError: unknown;
24
- /** HTTP response body text when the model returned content but parsing failed. */
25
- lastRawAssistantContent?: string;
26
- /** Tokens spent across billed-but-discarded attempts before every model failed. */
27
- wastedUsage?: LlmUsageStats;
28
- /** USD cost spent across billed-but-discarded attempts (undefined when no cost was reported). */
29
- wastedCost?: number;
30
- });
24
+ export declare class DocumentBatchAllModelsFailedError extends LlmAllModelsFailedError {
25
+ constructor(message: string, details: LlmAllModelsFailedDetails);
31
26
  }
32
27
  export interface LlmClientOptions {
33
28
  config: Pick<I18nConfig, "provider" | "providers" | "sourceLocale" | "localeDisplayNames">;
@@ -40,6 +35,17 @@ export interface LlmClientOptions {
40
35
  translationModels?: string[];
41
36
  /** Append request/response JSON when set. */
42
37
  debugTrafficFilePath?: string | null;
38
+ /**
39
+ * When set (CLI `--debug-failed` → `cacheDir`), write a `FAILED-TRANSLATION` file for each
40
+ * discarded model attempt (script/parse/API), including fallbacks — not only the final
41
+ * all-models-failed throw.
42
+ */
43
+ debugFailedDir?: string | null;
44
+ /**
45
+ * Path label used in `--debug-failed` logs for UI/plural/proofread batches. Document batches
46
+ * prefer `docLogContext.relativePath` when present.
47
+ */
48
+ debugFailedRelativePath?: string;
43
49
  logger?: Logger;
44
50
  httpReferer?: string;
45
51
  xTitle?: string;
@@ -67,6 +73,8 @@ export declare class LlmClient {
67
73
  private readonly maxTokens;
68
74
  private readonly temperature;
69
75
  private readonly debugTrafficFilePath;
76
+ private readonly debugFailedDir;
77
+ private readonly debugFailedRelativePath;
70
78
  private readonly logger?;
71
79
  private readonly localeDisplayNames;
72
80
  private readonly sourceLanguageLabel;
@@ -95,12 +103,21 @@ export declare class LlmClient {
95
103
  * a stray foreign-language quote does not fail the output, and `zh-Hans`/`zh-Hant` are told
96
104
  * apart via variant-distinct characters. Uses {@link effectiveScriptSubtag} so bare `hi`
97
105
  * (Devanagari by default) is enforced like an explicit `*-Deva` tag. Locales with no
98
- * effective script, and composite scripts (e.g. `Jpan`, `Kore`), are not enforced.
106
+ * effective script are not enforced. Composite families (`Jpan`, `Kore`) are enforced as
107
+ * allowed-script sets. Fully Latin leftover that is not a preserved brand/code token is
108
+ * rejected for non-Latin targets.
99
109
  */
100
110
  private assertExpectedScript;
111
+ private assertBatchExpectedScript;
101
112
  private appendDebugLog;
102
113
  /** Match doc-translate log lines: two-space indent, locale, filename. */
103
114
  private warnModelSwitch;
115
+ /**
116
+ * Persist prompt + raw output + validation error for a discarded model attempt when
117
+ * `--debug-failed` supplied `debugFailedDir`.
118
+ */
119
+ private logFailedModelAttempt;
120
+ private static promptPartsFromMessages;
104
121
  private toOpenRouterMessages;
105
122
  private static emptyDiscarded;
106
123
  /** Add a billed-but-discarded attempt's tokens/cost onto the running discarded total. */
@@ -1 +1 @@
1
- {"version":3,"file":"llm-client.d.ts","sourceRoot":"","sources":["../../src/api/llm-client.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EACL,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,OAAO,EACZ,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAC;AAc1B,OAAO,EAYL,KAAK,2BAA2B,EAChC,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC3B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AA2DjD,+FAA+F;AAC/F,qBAAa,iCAAkC,SAAQ,KAAK;aAGxC,OAAO,EAAE;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,kFAAkF;QAClF,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,mFAAmF;QACnF,WAAW,CAAC,EAAE,aAAa,CAAC;QAC5B,iGAAiG;QACjG,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;gBAZD,OAAO,EAAE,MAAM,EACC,OAAO,EAAE;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,kFAAkF;QAClF,uBAAuB,CAAC,EAAE,MAAM,CAAC;QACjC,mFAAmF;QACnF,WAAW,CAAC,EAAE,aAAa,CAAC;QAC5B,iGAAiG;QACjG,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB;CAKJ;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,oBAAoB,CAAC,CAAC;IAC3F,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,6CAA6C;IAC7C,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CACvE;AAED,gDAAgD;AAChD,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAEvD;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA2B;IAC5D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAW;IACvC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAgB;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAyB;IAC5D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAS;IAC7C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAA2D;gBAE3E,IAAI,EAAE,gBAAgB;IAkDlC,OAAO,CAAC,aAAa;IAuBrB,mBAAmB,IAAI,SAAS,MAAM,EAAE;IAIxC,oFAAoF;IACpF,WAAW,IAAI,MAAM;IAIrB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAa9B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,cAAc;IActB,yEAAyE;IACzE,OAAO,CAAC,eAAe;IAcvB,OAAO,CAAC,oBAAoB;IAM5B,OAAO,CAAC,MAAM,CAAC,cAAc;IAI7B,yFAAyF;IACzF,OAAO,CAAC,MAAM,CAAC,YAAY;IAa3B,2FAA2F;IAC3F,OAAO,CAAC,MAAM,CAAC,aAAa;IAiB5B,6FAA6F;IAC7F,OAAO,CAAC,WAAW;IAOnB,6FAA6F;YAC/E,eAAe;IA4EvB,IAAI,CACR,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,EAC3E,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,aAAa,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;QACzD,kGAAkG;QAClG,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KAC9C,GACA,OAAO,CAAC,YAAY,CAAC;IAqDxB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAOrC,wBAAwB,CAC5B,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,yBAAyB,CAAC;QACxC,aAAa,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1D,GACA,OAAO,CAAC,iBAAiB,CAAC;IAoCvB,sBAAsB,CAC1B,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,MAAM,EACd,aAAa,GAAE,MAAM,EAAO,EAC5B,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,yBAAyB,CAAC;QACxC,cAAc,CAAC,EAAE,2BAA2B,CAAC;QAC7C,oEAAoE;QACpE,aAAa,CAAC,EAAE;YAAE,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1C,GACA,OAAO,CAAC,sBAAsB,CAAC;IA4IlC;;OAEG;IACG,gBAAgB,CACpB,KAAK,EAAE,MAAM,EAAE,EACf,YAAY,EAAE,MAAM,EACpB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC/D,OAAO,CAAC;QACT,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,aAAa,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IA8DF;;OAEG;IACG,sBAAsB,CAC1B,KAAK,EAAE,MAAM,EAAE,EACf,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GAC/D,OAAO,CAAC;QACT,KAAK,EAAE,qBAAqB,EAAE,CAAC;QAC/B,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,aAAa,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;KAC9B,CAAC;IA8DF;;OAEG;IACG,4BAA4B,CAChC,aAAa,EAAE,cAAc,EAAE,EAC/B,QAAQ,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,EACvD,OAAO,CAAC,EAAE;QACR,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,yFAAyF;QACzF,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GACA,OAAO,CAAC;QACT,KAAK,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;QACtC,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,aAAa,CAAC;QACrB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,2EAA2E;QAC3E,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;CA+DH;AAED,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,kBAAY,CAAC;AAC1C,yCAAyC;AACzC,MAAM,MAAM,gBAAgB,GAAG,SAAS,CAAC"}
1
+ {"version":3,"file":"llm-client.d.ts","sourceRoot":"","sources":["../../src/api/llm-client.ts"],"names":[],"mappings":"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"}
@@ -4,10 +4,11 @@ import { generateText } from "ai";
4
4
  import { createOpenAICompatible, } from "@ai-sdk/openai-compatible";
5
5
  import { BatchTranslationError, } from "../core/types.js";
6
6
  import { englishLanguageNameForLocale, normalizeLocale, resolveTranslationModels, } from "../core/config.js";
7
- import { effectiveScriptSubtag, scriptValidationIssue } from "../core/locale-utils.js";
7
+ import { batchTranslationScriptIssue, effectiveScriptSubtag, scriptValidationIssue, } from "../core/locale-utils.js";
8
8
  import { OPENROUTER_PROVIDER_KEY, resolveActiveProvider, resolveApiKey, resolveProviderSettings, } from "../core/llm-providers.js";
9
9
  import { buildDocumentBatchPrompt, buildDocumentSinglePrompt, buildProofreadUIPromptMessages, buildUIPromptMessages, parseBatchJsonArrayResponse, parseBatchJsonObjectResponse, parseBatchTranslationResponse, parseProofreadUIBatchResponse, parsePluralFormsJsonResponse, parseUIJsonArrayResponse, ScriptValidationError, } from "../core/prompt-builder.js";
10
10
  import { assertPluralFormsPlaceholders } from "../core/plural-placeholders.js";
11
+ import { warnTranslationFailureLogPath, writeTranslationFailureLog, } from "../utils/translation-failure-log.js";
11
12
  /** OpenRouter: prefer throughput; allow backup providers (top-level `provider` routing field). */
12
13
  const OPENROUTER_PROVIDER = {
13
14
  sort: "throughput",
@@ -41,11 +42,18 @@ const openRouterMetadataExtractor = {
41
42
  buildMetadata: () => undefined,
42
43
  }),
43
44
  };
44
- /** Thrown when every model in the chain fails for {@link LlmClient.translateDocumentBatch}. */
45
- export class DocumentBatchAllModelsFailedError extends Error {
45
+ /** Thrown when every model in the chain fails for a translation request. */
46
+ export class LlmAllModelsFailedError extends Error {
46
47
  constructor(message, details) {
47
48
  super(message);
48
49
  this.details = details;
50
+ this.name = "LlmAllModelsFailedError";
51
+ }
52
+ }
53
+ /** Thrown when every model in the chain fails for {@link LlmClient.translateDocumentBatch}. */
54
+ export class DocumentBatchAllModelsFailedError extends LlmAllModelsFailedError {
55
+ constructor(message, details) {
56
+ super(message, details);
49
57
  this.name = "DocumentBatchAllModelsFailedError";
50
58
  }
51
59
  }
@@ -85,6 +93,8 @@ export class LlmClient {
85
93
  this.temperature = settings.temperature;
86
94
  this.requestTimeoutMs = settings.requestTimeoutMs;
87
95
  this.debugTrafficFilePath = opts.debugTrafficFilePath ?? null;
96
+ this.debugFailedDir = opts.debugFailedDir ?? null;
97
+ this.debugFailedRelativePath = opts.debugFailedRelativePath?.trim() || "llm-batch";
88
98
  this.logger = opts.logger;
89
99
  this.localeDisplayNames = {};
90
100
  for (const [k, v] of Object.entries(opts.config.localeDisplayNames ?? {})) {
@@ -153,20 +163,30 @@ export class LlmClient {
153
163
  * a stray foreign-language quote does not fail the output, and `zh-Hans`/`zh-Hant` are told
154
164
  * apart via variant-distinct characters. Uses {@link effectiveScriptSubtag} so bare `hi`
155
165
  * (Devanagari by default) is enforced like an explicit `*-Deva` tag. Locales with no
156
- * effective script, and composite scripts (e.g. `Jpan`, `Kore`), are not enforced.
166
+ * effective script are not enforced. Composite families (`Jpan`, `Kore`) are enforced as
167
+ * allowed-script sets. Fully Latin leftover that is not a preserved brand/code token is
168
+ * rejected for non-Latin targets.
157
169
  */
158
- assertExpectedScript(text, targetLocale) {
170
+ assertExpectedScript(text, targetLocale, sourceText) {
159
171
  const script = effectiveScriptSubtag(targetLocale);
160
172
  if (!script) {
161
173
  return;
162
174
  }
163
- const issue = scriptValidationIssue(text, script);
175
+ const issue = scriptValidationIssue(text, script, sourceText !== undefined ? { sourceText } : undefined);
164
176
  if (!issue) {
165
177
  return;
166
178
  }
167
179
  const suffix = issue.sample.length > 0 ? ` (${issue.sample.join(" ")})` : "";
168
180
  throw new ScriptValidationError(`Output for ${targetLocale} ${issue.message}${suffix}`, text, issue.sample);
169
181
  }
182
+ assertBatchExpectedScript(outputs, sources, targetLocale) {
183
+ const issue = batchTranslationScriptIssue(outputs, sources, targetLocale);
184
+ if (!issue) {
185
+ return;
186
+ }
187
+ const suffix = issue.sample.length > 0 ? ` (${issue.sample.join(" ")})` : "";
188
+ throw new ScriptValidationError(`Output for ${targetLocale} ${issue.message}${suffix}`, outputs.join("\n"), issue.sample);
189
+ }
170
190
  appendDebugLog(direction, payload) {
171
191
  if (!this.debugTrafficFilePath) {
172
192
  return;
@@ -187,6 +207,43 @@ export class LlmClient {
187
207
  const detail = error instanceof Error ? error.message : String(error);
188
208
  console.warn(chalk.yellow(` ⚠️ ${loc}: ${failedModel} failed (${detail}). Trying ${nextModel}…`));
189
209
  }
210
+ /**
211
+ * Persist prompt + raw output + validation error for a discarded model attempt when
212
+ * `--debug-failed` supplied `debugFailedDir`.
213
+ */
214
+ logFailedModelAttempt(args) {
215
+ if (!this.debugFailedDir) {
216
+ return;
217
+ }
218
+ const nextModel = this.modelsToTry[args.modelIndex + 1];
219
+ const errMsg = args.error instanceof Error ? args.error.message : String(args.error);
220
+ warnTranslationFailureLogPath(writeTranslationFailureLog({
221
+ cacheDirAbs: this.debugFailedDir,
222
+ relativePath: args.relativePath,
223
+ locale: args.locale,
224
+ segmentsLabel: args.segmentsLabel ?? "batch",
225
+ outcome: nextModel ? "retrying_next_model" : "fatal",
226
+ failedModel: args.model,
227
+ nextModel,
228
+ qualityErrors: [errMsg],
229
+ perSegmentLines: [],
230
+ systemPrompt: args.systemPrompt,
231
+ userContent: args.userContent,
232
+ rawAssistantContent: args.rawAssistantContent ?? "",
233
+ }));
234
+ }
235
+ static promptPartsFromMessages(messages) {
236
+ return {
237
+ systemPrompt: messages
238
+ .filter((m) => m.role === "system")
239
+ .map((m) => m.content)
240
+ .join("\n\n"),
241
+ userContent: messages
242
+ .filter((m) => m.role === "user")
243
+ .map((m) => m.content)
244
+ .join("\n\n"),
245
+ };
246
+ }
190
247
  toOpenRouterMessages(messages) {
191
248
  return messages.map((m) => ({ role: m.role, content: m.content }));
192
249
  }
@@ -288,9 +345,12 @@ export class LlmClient {
288
345
  const start = Math.max(0, Math.floor(options?.startModelIndex ?? 0));
289
346
  let lastError;
290
347
  const discarded = LlmClient.emptyDiscarded();
348
+ const promptParts = LlmClient.promptPartsFromMessages(messages);
349
+ const failedPath = options?.docLogContext?.relativePath ?? this.debugFailedRelativePath;
350
+ const failedLocale = options?.docLogContext?.locale ?? "unknown";
291
351
  for (let mi = start; mi < this.modelsToTry.length; mi++) {
292
352
  const model = this.modelsToTry[mi];
293
- const warnFailure = (e) => {
353
+ const recordFailure = (e, rawAssistantContent) => {
294
354
  const nextModel = this.modelsToTry[mi + 1];
295
355
  if (nextModel && options?.docLogContext) {
296
356
  this.warnModelSwitch(options.docLogContext.locale, options.docLogContext.relativePath, model, nextModel, e);
@@ -298,6 +358,16 @@ export class LlmClient {
298
358
  else if (!options?.docLogContext) {
299
359
  this.logger?.warn(`Model ${model} failed: ${e}`);
300
360
  }
361
+ this.logFailedModelAttempt({
362
+ locale: failedLocale,
363
+ relativePath: failedPath,
364
+ model,
365
+ modelIndex: mi,
366
+ error: e,
367
+ systemPrompt: promptParts.systemPrompt,
368
+ userContent: promptParts.userContent,
369
+ rawAssistantContent,
370
+ });
301
371
  };
302
372
  let completion;
303
373
  try {
@@ -308,7 +378,7 @@ export class LlmClient {
308
378
  if (e instanceof BilledCompletionError) {
309
379
  LlmClient.addDiscarded(discarded, e.usage, e.cost);
310
380
  }
311
- warnFailure(e);
381
+ recordFailure(e);
312
382
  continue;
313
383
  }
314
384
  try {
@@ -317,7 +387,7 @@ export class LlmClient {
317
387
  catch (e) {
318
388
  lastError = e;
319
389
  LlmClient.addDiscarded(discarded, completion.usage, completion.cost);
320
- warnFailure(e);
390
+ recordFailure(e, completion.content);
321
391
  continue;
322
392
  }
323
393
  const folded = LlmClient.foldDiscarded(completion.usage, completion.cost, discarded);
@@ -345,7 +415,7 @@ export class LlmClient {
345
415
  ], {
346
416
  startModelIndex: options?.startModelIndex,
347
417
  docLogContext: options?.docLogContext,
348
- validateResponse: (c) => this.assertExpectedScript(this.stripTranslateTags(c), targetLocale),
418
+ validateResponse: (c) => this.assertExpectedScript(this.stripTranslateTags(c), targetLocale, content),
349
419
  });
350
420
  return {
351
421
  content: this.stripTranslateTags(res.content),
@@ -410,6 +480,15 @@ export class LlmClient {
410
480
  this.logger?.warn(`Batch request failed with ${model}: ${e}`);
411
481
  }
412
482
  }
483
+ this.logFailedModelAttempt({
484
+ locale,
485
+ relativePath: options?.docLogContext?.relativePath ?? this.debugFailedRelativePath,
486
+ model,
487
+ modelIndex: mi,
488
+ error: e,
489
+ systemPrompt,
490
+ userContent,
491
+ });
413
492
  continue;
414
493
  }
415
494
  try {
@@ -423,9 +502,20 @@ export class LlmClient {
423
502
  else {
424
503
  translations = parseBatchTranslationResponse(completion.content, segments.length, completion.content);
425
504
  }
426
- for (const value of translations.values()) {
427
- this.assertExpectedScript(value, locale);
505
+ for (const [index, value] of translations.entries()) {
506
+ this.assertExpectedScript(value, locale, segments[index]?.content);
507
+ }
508
+ const batchOutputs = [];
509
+ const batchSources = [];
510
+ for (let i = 0; i < segments.length; i++) {
511
+ const value = translations.get(i);
512
+ if (value === undefined) {
513
+ continue;
514
+ }
515
+ batchOutputs.push(value);
516
+ batchSources.push(segments[i]?.content ?? "");
428
517
  }
518
+ this.assertBatchExpectedScript(batchOutputs, batchSources, locale);
429
519
  const folded = LlmClient.foldDiscarded(completion.usage, completion.cost, discarded);
430
520
  return {
431
521
  translations,
@@ -458,6 +548,16 @@ export class LlmClient {
458
548
  this.logger?.warn(`Batch parse failed with ${model}: ${e}`);
459
549
  }
460
550
  }
551
+ this.logFailedModelAttempt({
552
+ locale,
553
+ relativePath: options?.docLogContext?.relativePath ?? this.debugFailedRelativePath,
554
+ model,
555
+ modelIndex: mi,
556
+ error: e,
557
+ systemPrompt,
558
+ userContent,
559
+ rawAssistantContent: completion.content,
560
+ });
461
561
  }
462
562
  }
463
563
  const msg = `All translation models failed for batch (${this.modelsToTry.slice(start).join(", ")}). Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`;
@@ -503,6 +603,7 @@ export class LlmClient {
503
603
  const start = Math.max(0, Math.floor(options?.startModelIndex ?? 0));
504
604
  let lastError;
505
605
  const discarded = LlmClient.emptyDiscarded();
606
+ let lastFailureDetails;
506
607
  for (let mi = start; mi < this.modelsToTry.length; mi++) {
507
608
  const model = this.modelsToTry[mi];
508
609
  let result;
@@ -514,14 +615,31 @@ export class LlmClient {
514
615
  if (e instanceof BilledCompletionError) {
515
616
  LlmClient.addDiscarded(discarded, e.usage, e.cost);
516
617
  }
618
+ lastFailureDetails = {
619
+ systemPrompt,
620
+ userContent,
621
+ lastModel: model,
622
+ lastError: e,
623
+ };
517
624
  this.logger?.warn(`UI batch failed with ${model}: ${e}`);
625
+ this.logFailedModelAttempt({
626
+ locale: targetLocale,
627
+ relativePath: this.debugFailedRelativePath,
628
+ model,
629
+ modelIndex: mi,
630
+ error: e,
631
+ systemPrompt,
632
+ userContent,
633
+ segmentsLabel: "ui-batch",
634
+ });
518
635
  continue;
519
636
  }
520
637
  try {
521
638
  const translations = parseUIJsonArrayResponse(result.content, texts.length);
522
- for (const value of translations) {
523
- this.assertExpectedScript(value, targetLocale);
639
+ for (let i = 0; i < translations.length; i++) {
640
+ this.assertExpectedScript(translations[i], targetLocale, texts[i]);
524
641
  }
642
+ this.assertBatchExpectedScript(translations, texts, targetLocale);
525
643
  const folded = LlmClient.foldDiscarded(result.usage, result.cost, discarded);
526
644
  return {
527
645
  translations,
@@ -533,10 +651,33 @@ export class LlmClient {
533
651
  catch (e) {
534
652
  lastError = e;
535
653
  LlmClient.addDiscarded(discarded, result.usage, result.cost);
654
+ lastFailureDetails = {
655
+ systemPrompt,
656
+ userContent,
657
+ lastModel: model,
658
+ lastError: e,
659
+ lastRawAssistantContent: result.content,
660
+ };
536
661
  this.logger?.warn(`UI batch failed with ${model}: ${e}`);
662
+ this.logFailedModelAttempt({
663
+ locale: targetLocale,
664
+ relativePath: this.debugFailedRelativePath,
665
+ model,
666
+ modelIndex: mi,
667
+ error: e,
668
+ systemPrompt,
669
+ userContent,
670
+ rawAssistantContent: result.content,
671
+ segmentsLabel: "ui-batch",
672
+ });
537
673
  }
538
674
  }
539
- throw new Error(`All translation models failed for UI batch (${this.modelsToTry.slice(start).join(", ")}). Last error: ${lastError}`);
675
+ throw new LlmAllModelsFailedError(`All translation models failed for UI batch (${this.modelsToTry.slice(start).join(", ")}). Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`, lastFailureDetails ?? {
676
+ systemPrompt,
677
+ userContent,
678
+ lastModel: this.modelsToTry[Math.max(0, this.modelsToTry.length - 1)],
679
+ lastError,
680
+ });
540
681
  }
541
682
  /**
542
683
  * `proofread-ui`: review a batch of source-locale UI strings; model returns JSON array of `{ issues: [...] }`.
@@ -573,6 +714,16 @@ export class LlmClient {
573
714
  LlmClient.addDiscarded(discarded, e.usage, e.cost);
574
715
  }
575
716
  this.logger?.warn(`proofread-ui batch failed with ${model}: ${e}`);
717
+ this.logFailedModelAttempt({
718
+ locale: languageLabel,
719
+ relativePath: this.debugFailedRelativePath,
720
+ model,
721
+ modelIndex: mi,
722
+ error: e,
723
+ systemPrompt,
724
+ userContent,
725
+ segmentsLabel: "proofread-ui",
726
+ });
576
727
  continue;
577
728
  }
578
729
  try {
@@ -590,6 +741,17 @@ export class LlmClient {
590
741
  lastError = e;
591
742
  LlmClient.addDiscarded(discarded, result.usage, result.cost);
592
743
  this.logger?.warn(`proofread-ui batch failed with ${model}: ${e}`);
744
+ this.logFailedModelAttempt({
745
+ locale: languageLabel,
746
+ relativePath: this.debugFailedRelativePath,
747
+ model,
748
+ modelIndex: mi,
749
+ error: e,
750
+ systemPrompt,
751
+ userContent,
752
+ rawAssistantContent: result.content,
753
+ segmentsLabel: "proofread-ui",
754
+ });
593
755
  }
594
756
  }
595
757
  throw new Error(`All translation models failed for proofread-ui batch (${this.modelsToTry.slice(start).join(", ")}). Last error: ${lastError}`);
@@ -612,6 +774,7 @@ export class LlmClient {
612
774
  const start = Math.max(0, Math.floor(options?.startModelIndex ?? 0));
613
775
  let lastError;
614
776
  const discarded = LlmClient.emptyDiscarded();
777
+ let lastFailureDetails;
615
778
  for (let mi = start; mi < this.modelsToTry.length; mi++) {
616
779
  const model = this.modelsToTry[mi];
617
780
  let result;
@@ -623,7 +786,23 @@ export class LlmClient {
623
786
  if (e instanceof BilledCompletionError) {
624
787
  LlmClient.addDiscarded(discarded, e.usage, e.cost);
625
788
  }
789
+ lastFailureDetails = {
790
+ systemPrompt: messages.systemPrompt,
791
+ userContent: messages.userContent,
792
+ lastModel: model,
793
+ lastError: e,
794
+ };
626
795
  this.logger?.warn(`Plural cardinal batch failed with ${model}: ${e}`);
796
+ this.logFailedModelAttempt({
797
+ locale: options?.targetLocale ?? "unknown",
798
+ relativePath: this.debugFailedRelativePath,
799
+ model,
800
+ modelIndex: mi,
801
+ error: e,
802
+ systemPrompt: messages.systemPrompt,
803
+ userContent: messages.userContent,
804
+ segmentsLabel: "plural-batch",
805
+ });
627
806
  continue;
628
807
  }
629
808
  try {
@@ -636,7 +815,7 @@ export class LlmClient {
636
815
  }
637
816
  if (options?.targetLocale) {
638
817
  for (const value of Object.values(forms)) {
639
- this.assertExpectedScript(value, options.targetLocale);
818
+ this.assertExpectedScript(value, options.targetLocale, options.originalLiteral);
640
819
  }
641
820
  }
642
821
  const folded = LlmClient.foldDiscarded(result.usage, result.cost, discarded);
@@ -651,10 +830,33 @@ export class LlmClient {
651
830
  catch (e) {
652
831
  lastError = e;
653
832
  LlmClient.addDiscarded(discarded, result.usage, result.cost);
833
+ lastFailureDetails = {
834
+ systemPrompt: messages.systemPrompt,
835
+ userContent: messages.userContent,
836
+ lastModel: model,
837
+ lastError: e,
838
+ lastRawAssistantContent: result.content,
839
+ };
654
840
  this.logger?.warn(`Plural cardinal batch failed with ${model}: ${e}`);
841
+ this.logFailedModelAttempt({
842
+ locale: options?.targetLocale ?? "unknown",
843
+ relativePath: this.debugFailedRelativePath,
844
+ model,
845
+ modelIndex: mi,
846
+ error: e,
847
+ systemPrompt: messages.systemPrompt,
848
+ userContent: messages.userContent,
849
+ rawAssistantContent: result.content,
850
+ segmentsLabel: "plural-batch",
851
+ });
655
852
  }
656
853
  }
657
- throw new Error(`All translation models failed for plural cardinal batch (${this.modelsToTry.slice(start).join(", ")}). Last error: ${lastError}`);
854
+ throw new LlmAllModelsFailedError(`All translation models failed for plural cardinal batch (${this.modelsToTry.slice(start).join(", ")}). Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`, lastFailureDetails ?? {
855
+ systemPrompt: messages.systemPrompt,
856
+ userContent: messages.userContent,
857
+ lastModel: this.modelsToTry[Math.max(0, this.modelsToTry.length - 1)],
858
+ lastError,
859
+ });
658
860
  }
659
861
  }
660
862
  /** @deprecated Use {@link LlmClient}. Retained for backward-compatible imports. */