Added messageFormatter into i18nBuilder and support for choiceFormat#942
Added messageFormatter into i18nBuilder and support for choiceFormat#942vinckobb wants to merge 2 commits intokeycloakify:mainfrom
Conversation
WalkthroughAdds a ChoiceFormat parser Changes
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer
participant Builder as I18nBuilder
participant GetI18n as createGetI18n
participant Factory as translation factory
participant Resolver as resolveMsg
Note over Dev,Builder: Build without custom formatter
Dev->>Builder: .build()
Builder->>GetI18n: createGetI18n({ messageFormatter: undefined })
GetI18n->>Factory: createTranslationFunctions({ messageFormatter: undefined })
Factory->>Resolver: resolveMsg(message,args)
Resolver->>Resolver: formatChoiceMessage(message,args)
Resolver-->>Dev: formatted message
Note over Dev,Builder: Build with custom formatter
Dev->>Builder: .withMessageFormatter(customFn).build()
Builder->>GetI18n: createGetI18n({ messageFormatter: customFn })
GetI18n->>Factory: createTranslationFunctions({ messageFormatter: customFn })
Factory->>Resolver: resolveMsg(message,args)
Resolver->>Resolver: customFn(message,args)
Resolver-->>Dev: formatted message
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (2)
🧰 Additional context used🧬 Code graph analysis (3)src/login/i18n/noJsx/formatMessageChoiceFormat.ts (1)
src/login/i18n/withJsx/i18nBuilder.ts (3)
src/login/i18n/noJsx/getI18n.tsx (1)
🪛 ast-grep (0.39.6)src/login/i18n/noJsx/formatMessageChoiceFormat.ts[warning] 94-94: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns. (regexp-from-variable) 🔇 Additional comments (11)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/login/i18n/noJsx/i18nBuilder.ts (1)
112-129: Bug: messageFormatter lost across builder stepswithThemeName/withExtraLanguages/withCustomTranslations don't forward params.messageFormatter; chaining drops the formatter unless set last.
withThemeName: () => createI18nBuilder({ extraLanguageTranslations: params.extraLanguageTranslations, messagesByLanguageTag_themeDefined: - params.messagesByLanguageTag_themeDefined as any + params.messagesByLanguageTag_themeDefined as any, + messageFormatter: params.messageFormatter }), withExtraLanguages: extraLanguageTranslations => createI18nBuilder({ extraLanguageTranslations, messagesByLanguageTag_themeDefined: - params.messagesByLanguageTag_themeDefined as any + params.messagesByLanguageTag_themeDefined as any, + messageFormatter: params.messageFormatter }), withCustomTranslations: messagesByLanguageTag_themeDefined => createI18nBuilder({ extraLanguageTranslations: params.extraLanguageTranslations, - messagesByLanguageTag_themeDefined + messagesByLanguageTag_themeDefined, + messageFormatter: params.messageFormatter }),
🧹 Nitpick comments (8)
test/login/i18n.typelevel-spec.ts (1)
128-149: Type-level coverage for withMessageFormatter is good; add a tiny runtime checkConsider a small runtime spec ensuring the custom formatter is actually invoked (e.g., stub that records calls).
Would you like me to add an integration test that builds i18n with withMessageFormatter and asserts the stub was called?
test/login/formatMessageChoiceFormat.spec.ts (1)
1-310: Add boundary tests to match Java ChoiceFormat semanticsPlease add:
- ≤ semantics for “#”: e.g., "3#≤3|3< >3" with values 2, 3, 4.
- Interval coverage: "5# ≤5|10# ≤10|10< >10" with value 7 should pick the ≤10 branch.
- Negative/decimal values (e.g., -1, 2.5).
- Sparse/large indices (e.g., {0} and {10}) to ensure placeholder mapping by explicit index.
I can draft these additional cases if helpful.
src/login/i18n/noJsx/formatMessageChoiceFormat.ts (1)
106-139: Minor: simplify by dropping startIndex entirely after refactorAfter the above change, startIndex is unused in formatChoiceMessage; you can remove it for clarity.
src/login/i18n/withJsx/useI18n.tsx (1)
24-26: Ergonomics: default generic = never might block passing messageFormatterWith MessageFormatter defaulting to never, callers of createUseI18n can’t pass messageFormatter unless they specify the generic. If this factory is public, consider defaulting to MessageFormatter_I18n instead, or ensure builder paths always provide the generic.
If you prefer the current constraint, please confirm that external consumers don’t call createUseI18n directly.
Also applies to: 38-41, 118-118
src/login/i18n/noJsx/getI18n.tsx (2)
18-18: Unify and tighten MessageFormatter arg typeUse unknown over any to avoid unsoundness and align across API.
-export type MessageFormatter = (message: string, args: (any | undefined)[]) => string; +export type MessageFormatter = (message: string, args: (unknown | undefined)[]) => string;
352-357: Guard custom formatter; fallback on errorA throwing formatter will break rendering. Catch and fallback to default.
- if (messageFormatter) { - return messageFormatter(message, args); - } - - return formatChoiceMessage(message, args); + if (messageFormatter) { + try { + return messageFormatter(message, args); + } catch (e) { + console.warn("keycloakify:i18n messageFormatter error; falling back to ChoiceFormat", e); + } + } + return formatChoiceMessage(message, args);src/login/i18n/noJsx/i18nBuilder.ts (1)
62-70: Use the shared formatter type alias to avoid driftAccept the aliased type instead of re-stating the function signature.
- withMessageFormatter: ( - formatter: (message: string, args: (string | undefined)[]) => string - ) => I18nBuilder< + withMessageFormatter: ( + formatter: MessageFormatter_I18n + ) => I18nBuilder<src/login/i18n/withJsx/i18nBuilder.ts (1)
62-70: Type consistency: accept the alias for formatterMirror the central alias to prevent signature drift and ease refactors.
- withMessageFormatter: ( - formatter: (message: string, args: (string | undefined)[]) => string - ) => I18nBuilder< + withMessageFormatter: ( + formatter: MessageFormatter_I18n + ) => I18nBuilder<
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
src/login/i18n/noJsx/formatMessageChoiceFormat.ts(1 hunks)src/login/i18n/noJsx/getI18n.tsx(6 hunks)src/login/i18n/noJsx/i18nBuilder.ts(6 hunks)src/login/i18n/noJsx/index.ts(1 hunks)src/login/i18n/withJsx/i18nBuilder.ts(5 hunks)src/login/i18n/withJsx/useI18n.tsx(4 hunks)test/login/formatMessageChoiceFormat.spec.ts(1 hunks)test/login/i18n.typelevel-spec.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
src/login/i18n/noJsx/formatMessageChoiceFormat.ts (1)
src/login/i18n/noJsx/index.ts (1)
formatChoiceMessage(2-2)
src/login/i18n/withJsx/useI18n.tsx (1)
src/login/i18n/noJsx/getI18n.tsx (2)
MessageFormatter(18-18)createGetI18n(47-308)
src/login/i18n/noJsx/i18nBuilder.ts (3)
src/login/i18n/withJsx/i18nBuilder.ts (2)
I18nBuilder(11-77)i18nBuilder(151-154)src/login/i18n/noJsx/getI18n.tsx (2)
MessageFormatter(18-18)createGetI18n(47-308)src/login/i18n/withJsx/useI18n.tsx (1)
MessageFormatter_I18n(19-19)
test/login/i18n.typelevel-spec.ts (2)
src/login/i18n/noJsx/i18nBuilder.ts (1)
i18nBuilder(148-151)src/login/i18n/withJsx/i18nBuilder.ts (1)
i18nBuilder(151-154)
src/login/i18n/noJsx/getI18n.tsx (1)
src/login/i18n/noJsx/formatMessageChoiceFormat.ts (1)
formatChoiceMessage(106-140)
test/login/formatMessageChoiceFormat.spec.ts (2)
src/login/i18n/noJsx/formatMessageChoiceFormat.ts (1)
formatChoiceMessage(106-140)src/login/i18n/noJsx/index.ts (1)
formatChoiceMessage(2-2)
src/login/i18n/withJsx/i18nBuilder.ts (3)
src/login/i18n/noJsx/i18nBuilder.ts (2)
I18nBuilder(11-77)i18nBuilder(148-151)src/login/i18n/noJsx/getI18n.tsx (1)
MessageFormatter(18-18)src/login/i18n/withJsx/useI18n.tsx (2)
MessageFormatter_I18n(19-19)createUseI18n(21-147)
🪛 ast-grep (0.39.6)
src/login/i18n/noJsx/formatMessageChoiceFormat.ts
[warning] 94-94: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\{${i + startIndex}\\}, "g")
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🔇 Additional comments (3)
src/login/i18n/noJsx/index.ts (1)
2-2: Public re‑export looks goodExposing formatChoiceMessage from noJsx is fine and keeps API surface coherent.
src/login/i18n/withJsx/useI18n.tsx (2)
4-4: Good: messageFormatter is threaded into createGetI18nImporting and passing MessageFormatter through ensures parity with noJsx path.
19-20: Type re-export is helpfulRe-exporting MessageFormatter improves DX for consumers importing from withJsx.
…ed value handling
Fixes #938 by adding support for messageFormatter function into i18n using withMessageFormatter in i18nBuilder, and adding support for choiceFormat message format used in keycloak translations
Summary by CodeRabbit
New Features
Tests