Localize mismatch actions and polish locale UI
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é <emile@probo.com>
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "閉じる"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "닫기"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "Закрити"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": "关闭"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
})}
|
||||
</Button>
|
||||
<Button
|
||||
size={1}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
// SOFTWARE.
|
||||
|
||||
import { Menu } from "@base-ui/react/menu";
|
||||
import { CheckIcon } from "@phosphor-icons/react";
|
||||
import type { ComponentProps, ReactNode } from "react";
|
||||
|
||||
import { useDropdownContext } from "./context";
|
||||
@@ -31,7 +32,8 @@ export type DropdownRadioItemProps
|
||||
shortcut?: ReactNode;
|
||||
};
|
||||
|
||||
// A menu item with a radio dot indicator. Used inside a DropdownRadioGroup.
|
||||
// A menu item with a checkmark for the selected value. Used inside a
|
||||
// DropdownRadioGroup (same indicator treatment as DropdownCheckboxItem).
|
||||
export function DropdownRadioItem(props: DropdownRadioItemProps) {
|
||||
const { className, shortcut, children, ...rest } = props;
|
||||
const { size, variant, highContrast } = useDropdownContext();
|
||||
@@ -42,7 +44,9 @@ export function DropdownRadioItem(props: DropdownRadioItemProps) {
|
||||
{...rest}
|
||||
>
|
||||
<span className={dropdownItemIndicator()}>
|
||||
<Menu.RadioItemIndicator className="size-1.5 rounded-full bg-current" />
|
||||
<Menu.RadioItemIndicator>
|
||||
<CheckIcon weight="bold" />
|
||||
</Menu.RadioItemIndicator>
|
||||
</span>
|
||||
<span className={dropdownItemLabel()}>{children}</span>
|
||||
{shortcut != null && <span className={dropdownItemShortcut()}>{shortcut}</span>}
|
||||
|
||||
Reference in New Issue
Block a user