-
Notifications
You must be signed in to change notification settings - Fork 663
feat(locale-switcher): support rich locale configs (string[] | Record | LocaleConfig[]) (#1084) #1464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(locale-switcher): support rich locale configs (string[] | Record | LocaleConfig[]) (#1084) #1464
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,22 +3,88 @@ | |
| import { useState, useEffect } from "react"; | ||
| import { getLocaleFromCookies, setLocaleInCookies } from "./utils"; | ||
|
|
||
| /** | ||
| * Represents a detailed locale configuration. | ||
| */ | ||
| export type LocaleConfig = { | ||
| /** The locale code (e.g., "en", "es-MX"). */ | ||
| code: string; | ||
| /** The name to display for the locale (e.g., "English", "Español"). */ | ||
| displayName: string; | ||
| /** An optional flag icon or component string. */ | ||
| flag?: string; | ||
| /** The native name of the language (e.g., "English", "Español"). */ | ||
| nativeName?: string; | ||
| }; | ||
|
|
||
| /** | ||
| * The type for the `locales` prop, allowing for simple strings, | ||
| * a key-value object, or a full configuration array. | ||
| */ | ||
| export type LocalesProp = string[] | Record<string, string> | LocaleConfig[]; | ||
|
|
||
| /** | ||
| * The props for the `LocaleSwitcher` component. | ||
| */ | ||
| export type LocaleSwitcherProps = { | ||
| /** | ||
| * An array of locale codes to display in the dropdown. | ||
| * | ||
| * This should contain both the source and target locales. | ||
| * The locales to display in the dropdown. Can be an array of strings, | ||
| * an object mapping locale codes to display names, or an array of | ||
| * `LocaleConfig` objects. | ||
| */ | ||
| locales: string[]; | ||
| locales: LocalesProp; | ||
| /** | ||
| * A custom class name for the dropddown's `select` element. | ||
| * A custom class name for the dropdown's `select` element. | ||
| */ | ||
| className?: string; | ||
| }; | ||
|
|
||
| /** | ||
| * Normalizes the `locales` prop into a consistent `LocaleConfig[]` format. | ||
| * @param locales - The `locales` prop to normalize. | ||
| * @returns An array of `LocaleConfig` objects. | ||
| */ | ||
| export function normalizeLocales(locales: LocalesProp): LocaleConfig[] { | ||
| if (Array.isArray(locales)) { | ||
| if (locales.length === 0) return []; | ||
|
|
||
| const isStringArray = locales.every((item) => typeof item === "string"); | ||
| const isObjectArray = locales.every( | ||
| (item) => typeof item === "object" && item !== null, | ||
| ); | ||
|
|
||
| if (isStringArray) { | ||
| return (locales as string[]).map((code) => ({ | ||
| code, | ||
| displayName: code, | ||
| })); | ||
| } | ||
|
|
||
| if (isObjectArray) { | ||
| const isLocaleConfigArray = locales.every( | ||
| (item: any) => "code" in item && "displayName" in item, | ||
| ); | ||
|
|
||
| if (!isLocaleConfigArray) { | ||
| throw new Error( | ||
| "Invalid LocaleConfig array provided. Each object must have 'code' and 'displayName' properties.", | ||
| ); | ||
| } | ||
| return locales as LocaleConfig[]; | ||
| } | ||
| } else if (typeof locales === "object" && locales !== null) { | ||
| // Handle Record<string, string> | ||
| return Object.entries(locales).map(([code, displayName]) => ({ | ||
| code, | ||
| displayName, | ||
| })); | ||
| } | ||
|
|
||
| throw new Error( | ||
| "Invalid 'locales' prop provided. It must be an array of strings, an array of LocaleConfig objects, or a Record<string, string>.", | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * An unstyled dropdown for switching between locales. | ||
| * | ||
|
|
@@ -37,21 +103,24 @@ export type LocaleSwitcherProps = { | |
| * <header> | ||
| * <nav> | ||
| * <LocaleSwitcher locales={["en", "es"]} /> | ||
| * <LocaleSwitcher locales={{ en: "English", es: "Español" }} /> | ||
| * <LocaleSwitcher locales={[{ code: "en", displayName: "English" }]} /> | ||
| * </nav> | ||
| * </header> | ||
| * ); | ||
| * } | ||
| * ``` | ||
| */ | ||
| export function LocaleSwitcher(props: LocaleSwitcherProps) { | ||
| const { locales } = props; | ||
| const normalizedLocales = normalizeLocales(props.locales); | ||
| const localeCodes = normalizedLocales.map((l) => l.code); | ||
| const [locale, setLocale] = useState<string | undefined>(undefined); | ||
|
|
||
| useEffect(() => { | ||
| const currentLocale = getLocaleFromCookies(); | ||
| const isValidLocale = currentLocale && locales.includes(currentLocale); | ||
| setLocale(isValidLocale ? currentLocale : locales[0]); | ||
| }, [locales]); | ||
| const isValidLocale = currentLocale && localeCodes.includes(currentLocale); | ||
| setLocale(isValidLocale ? currentLocale : localeCodes[0]); | ||
| }, [localeCodes]); | ||
|
Comment on lines
+115
to
+123
|
||
|
|
||
| if (locale === undefined) { | ||
| return null; | ||
|
|
@@ -65,9 +134,9 @@ export function LocaleSwitcher(props: LocaleSwitcherProps) { | |
| handleLocaleChange(e.target.value); | ||
| }} | ||
| > | ||
| {locales.map((locale) => ( | ||
| <option key={locale} value={locale}> | ||
| {locale} | ||
| {normalizedLocales.map((localeConfig) => ( | ||
| <option key={localeConfig.code} value={localeConfig.code}> | ||
| {localeConfig.displayName} | ||
| </option> | ||
| ))} | ||
| </select> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for handling mixed arrays (e.g.,
['en', { code: 'es', displayName: 'Español' }]) falls through to the generic error at line 83-85 without clear indication that mixed types are not allowed. When bothisStringArrayandisObjectArrayare false (lines 56 and 63), the code continues past line 74 without handling this case explicitly, making the control flow unclear. Consider adding an explicit check and error message for mixed arrays between lines 62-75.