Fix for issue #875: Custom translations for one language showing for other languages#876
Fix for issue #875: Custom translations for one language showing for other languages#876BrDewit wants to merge 5 commits intokeycloakify:mainfrom
Conversation
…tions to be shown for languages that don't have a custom translation for a certain key
WalkthroughAdds a two-tier fallback resolution for theme-defined translations: look up theme-defined messages for the current language, then the default message set for current language, then theme-defined for a fallback language, then the default set for the fallback language; introduces helper getThemeDefinedMessage and a new messages_themeDefined_fallback parameter. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant getI18n
participant messages_fromKcServer
participant messages_themeDefined_current
participant messages_default_current
participant messages_themeDefined_fallback
participant messages_default_fallback
Caller->>getI18n: request translation(key, themeName, currentLang, fallbackLang?)
getI18n->>messages_fromKcServer: lookup(key)
alt found in messages_fromKcServer
messages_fromKcServer-->>getI18n: value
getI18n-->>Caller: value
else not found
getI18n->>messages_themeDefined_current: getThemeDefinedMessage(key, currentLang, themeName)
alt found in theme-defined current
messages_themeDefined_current-->>getI18n: value (string or themed map -> selected by themeName)
getI18n-->>Caller: value
else
getI18n->>messages_default_current: lookup(key, currentLang)
alt found in default current
messages_default_current-->>getI18n: value
getI18n-->>Caller: value
else
getI18n->>messages_themeDefined_fallback: getThemeDefinedMessage(key, fallbackLang, themeName)
alt found in theme-defined fallback
messages_themeDefined_fallback-->>getI18n: value
getI18n-->>Caller: value
else
messages_default_fallback-->>getI18n: lookup(key, fallbackLang)
getI18n-->>Caller: value
end
end
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
Hello @BrDewit, Thank you for the well detailed PR. That being said I think this changes, while fixing the issue of overwriting the curstom translation of an existing key in only a specific language this change also cause important regression when defining custom message key. .withCustomTranslations({
en: {
myCustomMessage: "Hello world"
}
})With this PR applied, if the current language is Defining custom, non standard message keys is the primary usecase of What would be a reasonable approach is, if the key is a known one then do what you suggest otherwise keep the current behavior.... But even then, you usually never need to overwrite one of the default message keys, in your case you do just because the translation file are outdated and I have to update them. This is debatable though but as it stands the rule of |
|
Hi @garronej That's a very reasonable remark, if they did not specify the custom message, you would indeed want to fall back to an existing translation as that's better than just having the key inserted in that position. Especially as that's probably a more common case than the one where a custom translation is given for an existing key. When I have time, I'll revise the changes to fix this regression. |
… translations to be shown for languages that don't have a custom translation for a certain key" This reverts commit 252cf5e.
…s in a provided language from custom translations in the fallback language, and resolve to these sets in different places of the translation resolution logic
There was a problem hiding this comment.
Actionable comments posted: 0
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/getI18n.tsx (1)
317-326: Fix fallback order to avoid cross-language custom translations for default keys (even before async load completes)As written, when the current language is not the fallback language and before
messages_defaultSet_currentLanguageis loaded, known/default keys will fall back tomessages_themeDefined_fallback(custom translations from another language) instead of the default set fallback. This reintroduces the regression the PR aims to fix during the preload window.Branch resolution based on whether the key is a known default key:
- For default keys: never fall back to theme-defined messages from another language; fall back to the default set instead.
- For unknown/custom keys: preserve cross-language theme-defined fallback.
Apply this diff inside
resolveMsg:- const message = - id<Record<string, string | undefined>>(messages_fromKcServer)[key] ?? - getThemeDefinedMessage(messages_themeDefined, key) ?? - id<Record<string, string | undefined> | undefined>(messages_defaultSet_currentLanguage)?.[key] ?? - getThemeDefinedMessage(messages_themeDefined_fallback, key) ?? - id<Record<string, string | undefined>>(messages_defaultSet_fallbackLanguage)[key]; + const isKnownDefaultKey = Object.prototype.hasOwnProperty.call( + id<Record<string, string>>(messages_defaultSet_fallbackLanguage), + key + ); + + const message = isKnownDefaultKey + ? ( + id<Record<string, string | undefined>>(messages_fromKcServer)[key] ?? + getThemeDefinedMessage(messages_themeDefined, key) ?? + id<Record<string, string | undefined> | undefined>(messages_defaultSet_currentLanguage)?.[key] ?? + id<Record<string, string | undefined>>(messages_defaultSet_fallbackLanguage)[key] + ) + : ( + id<Record<string, string | undefined>>(messages_fromKcServer)[key] ?? + getThemeDefinedMessage(messages_themeDefined, key) ?? + getThemeDefinedMessage(messages_themeDefined_fallback, key) + );This preserves the intended behavior for both categories of keys and eliminates the transient regression before the async default-set load completes.
🧹 Nitpick comments (3)
src/login/i18n/noJsx/getI18n.tsx (3)
222-231: Clarify and harden selection of messages_themeDefined_fallback (minor determinism nit)Using the first key from
messagesByLanguageTag_themeDefinedas a tertiary fallback can be order-dependent. It’s fine, but to make the behavior fully deterministic and self-documenting, prefer trying known language tags in a stable order before falling back to “first available”.messages_themeDefined_fallback: - messagesByLanguageTag_themeDefined[id<string>(FALLBACK_LANGUAGE_TAG) as LanguageTag] ?? - (() => { - const firstLanguageTag = Object.keys(messagesByLanguageTag_themeDefined)[0]; - if (firstLanguageTag === undefined) { - return undefined; - } - return messagesByLanguageTag_themeDefined[firstLanguageTag as LanguageTag]; - })(), + messagesByLanguageTag_themeDefined[id<string>(FALLBACK_LANGUAGE_TAG) as LanguageTag] ?? + // Try default-set languages in declared order for a stable pick: + (id<readonly string[]>(languageTags_defaultSet) + .map(tag => messagesByLanguageTag_themeDefined[tag as LanguageTag]) + .find(Boolean) as typeof messages_themeDefined | undefined) ?? + // Finally, first available user-provided language (if any): + Object.values(messagesByLanguageTag_themeDefined)[0],
355-372: Helper extraction LGTM; optional: consider soft-failing when theme variant missingThe helper is clean and typed appropriately. The assert on missing
themeNamein a map is strict; if you prefer graceful degradation, you could returnundefinedhere to continue fallback rather than throwing. Keep as-is if the assert is intentional.
220-233: Add tests for the two key scenariosPlease add minimal tests to lock the intended behavior:
- Known/default key with custom translation only in another language: never fall back to that custom translation (should use default-set).
- Unknown/custom key with custom translation only in another language: do fall back to that custom translation.
I can draft these tests if you want.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/login/i18n/noJsx/getI18n.tsx(4 hunks)
🔇 Additional comments (1)
src/login/i18n/noJsx/getI18n.tsx (1)
304-311: Signature extension looks goodExtending
createI18nTranslationFunctionsFactorywithmessages_themeDefined_fallbackis coherent with the new lookup path. The destructuring is correct and call site updated in this file.
|
@garronej excuse me for the delay, but I've finally made the change. The translation logic will now first check in the custom translation of the provided language, then it will check the default set of the provided language, then it will check the custom translation of the fallback language, after which it will check the default set of the fallback language. This way, my issue, where a custom translation in 1 language would override the default translation of that key in every language, in is fixed. The only breaking change is of course if someone used the behaviour I changed, as a feature, where they use one custom translation to override default keys for every language, but that behaviour is unintuitive. It seems reasonable that with |
|
Hi @garronej I recently remembered that my pull request was still open. I've updated my fork with the latest changes, and the changes I made still seem relevant. If you agree with the changes, the pull request can be merged. If not, let me know what needs to be changed! Thanks in advance and happy holidays :) |
In issue #875 I outlined an issue where creating a custom translation for one language, caused all other languages to display that custom translation, instead of only the specified language displaying the custom translation.
The cause of the issue was due to the fact that the code fell back on the custom translations of other languages when creating the translation function of a specific language. If there was no custom translation for the language at all, for any key, the custom translation of either the default language, or if that didn't exist, the first specified custom translation, was used.
If any custom translation existed for a certain language, the issue would not occur as the
messagesByLanguageTag_themeDefined[currentLanguage.languageTag]call on line 223 of the getI18n.tsx file does not return undefined in this case, and it would not fall back on another language.I've removed the fallback to other languages in this part of the code, as the custom translation is supposed to only be applied to the language it's specified for. The resolveMsg and resolveMsgAdvanced functions handle falling back to other languages if a certain key does not exist.
I've tested the effects of the changes manually by testing multiple configurations of different languages and translations, but I've not implemented an automated test.
The change is only breaking if the user of the library used a custom translation of an existing key in 1 language to translate that same key across multiple/all other languages.
Summary by CodeRabbit
New Features
Refactor
Bug Fixes