From 5d161173c8e410d09812052c418efdb924c88804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 21 Jul 2026 16:21:55 +0200 Subject: [PATCH] Localize mismatch actions and polish locale UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render the switch-to-saved button in the identity locale, translate remaining locale stubs, and use a checkmark for selected dropdown radio items. Signed-off-by: Émile Ré --- .../compliance-portal/src/_locales/id-ID.json | 12 +++--- .../compliance-portal/src/_locales/it-IT.json | 12 +++--- .../compliance-portal/src/_locales/ja-JP.json | 12 +++--- .../compliance-portal/src/_locales/ko-KR.json | 12 +++--- .../compliance-portal/src/_locales/pl-PL.json | 12 +++--- .../compliance-portal/src/_locales/pt-PT.json | 12 +++--- .../compliance-portal/src/_locales/tr-TR.json | 12 +++--- .../compliance-portal/src/_locales/uk-UA.json | 12 +++--- .../compliance-portal/src/_locales/zh-CN.json | 12 +++--- .../src/components/LocaleMismatchCallout.tsx | 38 +++++++++++++++++-- .../ui/src/v2/Dropdown/DropdownRadioItem.tsx | 8 +++- 11 files changed, 94 insertions(+), 60 deletions(-) diff --git a/apps/compliance-portal/src/_locales/id-ID.json b/apps/compliance-portal/src/_locales/id-ID.json index 6e2a8b308..4fe1e4ecd 100644 --- a/apps/compliance-portal/src/_locales/id-ID.json +++ b/apps/compliance-portal/src/_locales/id-ID.json @@ -95,13 +95,13 @@ "next": "Halaman berikutnya" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "Bahasa", + "updateFailed": "Tidak dapat memperbarui bahasa", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "Anda sedang melihat halaman ini dalam {{language}}.", + "switchToMine": "Beralih ke {{language}}", + "useThis": "Gunakan {{language}} sebagai bahasa saya", + "dismiss": "Tutup" } } } diff --git a/apps/compliance-portal/src/_locales/it-IT.json b/apps/compliance-portal/src/_locales/it-IT.json index 8bfdfd51b..1e008da70 100644 --- a/apps/compliance-portal/src/_locales/it-IT.json +++ b/apps/compliance-portal/src/_locales/it-IT.json @@ -95,13 +95,13 @@ "next": "Pagina successiva" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "Lingua", + "updateFailed": "Impossibile aggiornare la lingua", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "Stai visualizzando questa pagina in {{language}}.", + "switchToMine": "Passa a {{language}}", + "useThis": "Usa {{language}} come mia lingua", + "dismiss": "Ignora" } } } diff --git a/apps/compliance-portal/src/_locales/ja-JP.json b/apps/compliance-portal/src/_locales/ja-JP.json index 82b8b1906..67b0582ed 100644 --- a/apps/compliance-portal/src/_locales/ja-JP.json +++ b/apps/compliance-portal/src/_locales/ja-JP.json @@ -95,13 +95,13 @@ "next": "次のページ" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "言語", + "updateFailed": "言語を更新できませんでした", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "このページは{{language}}で表示されています。", + "switchToMine": "{{language}}に切り替え", + "useThis": "{{language}}を自分の言語として使用", + "dismiss": "閉じる" } } } diff --git a/apps/compliance-portal/src/_locales/ko-KR.json b/apps/compliance-portal/src/_locales/ko-KR.json index 447b402cd..f1515f34d 100644 --- a/apps/compliance-portal/src/_locales/ko-KR.json +++ b/apps/compliance-portal/src/_locales/ko-KR.json @@ -95,13 +95,13 @@ "next": "다음 페이지" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "언어", + "updateFailed": "언어를 업데이트하지 못했습니다", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "{{language}}(으)로 이 페이지를 보고 있습니다.", + "switchToMine": "{{language}}(으)로 전환", + "useThis": "{{language}}을(를) 내 언어로 사용", + "dismiss": "닫기" } } } diff --git a/apps/compliance-portal/src/_locales/pl-PL.json b/apps/compliance-portal/src/_locales/pl-PL.json index 4bfa863dd..a55e89769 100644 --- a/apps/compliance-portal/src/_locales/pl-PL.json +++ b/apps/compliance-portal/src/_locales/pl-PL.json @@ -95,13 +95,13 @@ "next": "Następna strona" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "Język", + "updateFailed": "Nie udało się zaktualizować języka", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "Przeglądasz tę stronę w języku: {{language}}.", + "switchToMine": "Przełącz na {{language}}", + "useThis": "Użyj {{language}} jako mojego języka", + "dismiss": "Zamknij" } } } diff --git a/apps/compliance-portal/src/_locales/pt-PT.json b/apps/compliance-portal/src/_locales/pt-PT.json index 003760277..267c3c7c6 100644 --- a/apps/compliance-portal/src/_locales/pt-PT.json +++ b/apps/compliance-portal/src/_locales/pt-PT.json @@ -95,13 +95,13 @@ "next": "Página seguinte" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "Idioma", + "updateFailed": "Não foi possível atualizar o idioma", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "Está a ver esta página em {{language}}.", + "switchToMine": "Mudar para {{language}}", + "useThis": "Usar {{language}} como o meu idioma", + "dismiss": "Ignorar" } } } diff --git a/apps/compliance-portal/src/_locales/tr-TR.json b/apps/compliance-portal/src/_locales/tr-TR.json index 1e0da6dfa..9f6d93e5c 100644 --- a/apps/compliance-portal/src/_locales/tr-TR.json +++ b/apps/compliance-portal/src/_locales/tr-TR.json @@ -95,13 +95,13 @@ "next": "Sonraki sayfa" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "Dil", + "updateFailed": "Dil güncellenemedi", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "Bu sayfayı {{language}} olarak görüntülüyorsunuz.", + "switchToMine": "{{language}} diline geç", + "useThis": "{{language}} dilini benim dilim olarak kullan", + "dismiss": "Kapat" } } } diff --git a/apps/compliance-portal/src/_locales/uk-UA.json b/apps/compliance-portal/src/_locales/uk-UA.json index 8af09adf1..506d044d4 100644 --- a/apps/compliance-portal/src/_locales/uk-UA.json +++ b/apps/compliance-portal/src/_locales/uk-UA.json @@ -95,13 +95,13 @@ "next": "Наступна сторінка" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "Мова", + "updateFailed": "Не вдалося оновити мову", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "Ви переглядаєте цю сторінку мовою {{language}}.", + "switchToMine": "Перейти на {{language}}", + "useThis": "Використовувати {{language}} як мою мову", + "dismiss": "Закрити" } } } diff --git a/apps/compliance-portal/src/_locales/zh-CN.json b/apps/compliance-portal/src/_locales/zh-CN.json index 68c514577..bdfe251ee 100644 --- a/apps/compliance-portal/src/_locales/zh-CN.json +++ b/apps/compliance-portal/src/_locales/zh-CN.json @@ -95,13 +95,13 @@ "next": "下一页" }, "locale": { - "label": "Language", - "updateFailed": "Couldn't update language", + "label": "语言", + "updateFailed": "无法更新语言", "mismatch": { - "message": "You're viewing this page in {{language}}.", - "switchToMine": "Switch to {{language}}", - "useThis": "Use {{language}} as my language", - "dismiss": "Dismiss" + "message": "您正在以{{language}}查看此页面。", + "switchToMine": "切换到{{language}}", + "useThis": "将{{language}}设为我的语言", + "dismiss": "关闭" } } } diff --git a/apps/compliance-portal/src/components/LocaleMismatchCallout.tsx b/apps/compliance-portal/src/components/LocaleMismatchCallout.tsx index b5957302b..fea3048d9 100644 --- a/apps/compliance-portal/src/components/LocaleMismatchCallout.tsx +++ b/apps/compliance-portal/src/components/LocaleMismatchCallout.tsx @@ -22,13 +22,15 @@ import { GlobeIcon, XIcon } from "@phosphor-icons/react"; import { Button } from "@probo/ui/src/v2/Button/Button"; import { IconButton } from "@probo/ui/src/v2/IconButton/IconButton"; import { Text } from "@probo/ui/src/v2/typography/Text"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { graphql, useFragment } from "react-relay"; +import { DEFAULT_NAMESPACE } from "#/lib/i18n/backend"; import { isUrlLocale, URL_LOCALE_LABELS, + urlLocaleToLanguage, } from "#/lib/i18n/locale"; import { useChangeLocale } from "#/lib/i18n/useChangeLocale"; import { useLocale } from "#/lib/i18n/useLocale"; @@ -49,16 +51,39 @@ interface LocaleMismatchCalloutProps { // Full-bleed notice when the URL locale differs from the signed-in identity // preference. Dismissed state is React-only (no localStorage/cookies). export function LocaleMismatchCallout({ identityKey }: LocaleMismatchCalloutProps) { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); const identity = useFragment(localeMismatchCalloutFragment, identityKey); const urlLocale = useLocale(); const [changeLocale, isChanging] = useChangeLocale(); const [updateLocale, isUpdating] = useUpdateLocale(); const [dismissed, setDismissed] = useState(false); + // Bumps after the identity-locale catalog loads so the switch button can + // re-render in that language (it may not be the active i18n language). + const [, setSavedCatalogTick] = useState(0); const savedLocale = isUrlLocale(identity.locale) ? identity.locale : null; + const savedLanguage = savedLocale != null ? urlLocaleToLanguage(savedLocale) : null; + const visible = !dismissed && savedLocale != null && savedLocale !== urlLocale; - if (dismissed || savedLocale == null || savedLocale === urlLocale) { + useEffect(() => { + if (!visible || savedLanguage == null) { + return; + } + if (i18n.hasResourceBundle(savedLanguage, DEFAULT_NAMESPACE)) { + return; + } + let cancelled = false; + void i18n.loadLanguages(savedLanguage).then(() => { + if (!cancelled) { + setSavedCatalogTick(tick => tick + 1); + } + }); + return () => { + cancelled = true; + }; + }, [visible, savedLanguage, i18n]); + + if (!visible || savedLocale == null || savedLanguage == null) { return null; } @@ -105,7 +130,12 @@ export function LocaleMismatchCallout({ identityKey }: LocaleMismatchCalloutProp className="max-md:w-full" onClick={switchToSaved} > - {t("locale.mismatch.switchToMine", { language: savedLabel })} + {t("locale.mismatch.switchToMine", { + language: savedLabel, + // Label this action in the user's saved locale so it reads as + // "switch back to my language", not the page they're visiting. + lng: savedLanguage, + })}