diff --git a/src/api/utils/index.ts b/src/api/utils/index.ts index eb64c68..b1dfe5a 100644 --- a/src/api/utils/index.ts +++ b/src/api/utils/index.ts @@ -14,6 +14,7 @@ export * from './PlaySound'; export * from './PrefixUtils'; export * from './ProductImageUtility'; export * from './Randomizer'; +export * from './localizeWithFallback'; export * from './RememberLogin'; export * from './isSafeExternalUrl'; export * from './RoomChatFormatter'; diff --git a/src/api/utils/localizeWithFallback.test.ts b/src/api/utils/localizeWithFallback.test.ts new file mode 100644 index 0000000..4480671 --- /dev/null +++ b/src/api/utils/localizeWithFallback.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; + +import { resolveLocalized } from './localizeWithFallback'; + +/** + * The localization manager returns the KEY itself when a translation is missing + * (LocalizationManager.getValue → `value || key`). `resolveLocalized` turns that + * "missing" signal into a caller-supplied fallback so raw keys like + * `purse.seasonal.currency.11` never reach the UI. + */ +describe('resolveLocalized', () => +{ + it('returns the localized text when it differs from the key', () => + { + expect(resolveLocalized('Pixels', 'purse.seasonal.currency.5', 'fallback')).toBe('Pixels'); + }); + + it('returns the fallback when the text equals the key (missing translation)', () => + { + expect(resolveLocalized('purse.seasonal.currency.11', 'purse.seasonal.currency.11', '')).toBe(''); + expect(resolveLocalized('purse.seasonal.currency.11', 'purse.seasonal.currency.11', 'Currency')).toBe('Currency'); + }); + + it('returns the fallback for empty / null text', () => + { + expect(resolveLocalized('', 'some.key', 'FB')).toBe('FB'); + expect(resolveLocalized(null as unknown as string, 'some.key', 'FB')).toBe('FB'); + }); +}); diff --git a/src/api/utils/localizeWithFallback.ts b/src/api/utils/localizeWithFallback.ts new file mode 100644 index 0000000..4d33e11 --- /dev/null +++ b/src/api/utils/localizeWithFallback.ts @@ -0,0 +1,16 @@ +import { LocalizeText } from './LocalizeText'; + +/** + * Pure decision: the localization manager returns the key itself for a missing + * translation, so treat "text equals the key" (or empty) as "no translation" + * and use the fallback instead. + */ +export const resolveLocalized = (localized: string, key: string, fallback: string): string => + ((localized && (localized !== key)) ? localized : fallback); + +/** + * Localize `key`, returning `fallback` when the key has no translation + * (so raw keys never surface in the UI). + */ +export const localizeWithFallback = (key: string, fallback: string): string => + resolveLocalized(LocalizeText(key), key, fallback); diff --git a/src/components/purse/PurseView.tsx b/src/components/purse/PurseView.tsx index 60786fe..46037a6 100644 --- a/src/components/purse/PurseView.tsx +++ b/src/components/purse/PurseView.tsx @@ -1,18 +1,12 @@ import { CreateLinkEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback, useMemo, useState } from 'react'; import { FaChartBar, FaCog, FaSignOutAlt } from 'react-icons/fa'; -import { ClearRememberLogin, GetConfigurationValue, GetRememberLogin, LocalizeText } from '../../api'; +import { ClearRememberLogin, GetConfigurationValue, GetRememberLogin, localizeWithFallback, LocalizeText } from '../../api'; import { Column, LayoutCurrencyIcon } from '../../common'; import { usePurse } from '../../hooks'; import { CurrencyView } from './views/CurrencyView'; import { SeasonalView } from './views/SeasonalView'; -const localizeWithFallback = (key: string, fallback: string) => -{ - const text = LocalizeText(key); - return (text && text !== key) ? text : fallback; -}; - export const PurseView: FC<{}> = props => { const { purse = null, hcDisabled = false } = usePurse(); diff --git a/src/components/purse/views/SeasonalView.tsx b/src/components/purse/views/SeasonalView.tsx index 6cbfb8b..a79a8b8 100644 --- a/src/components/purse/views/SeasonalView.tsx +++ b/src/components/purse/views/SeasonalView.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { GetConfigurationValue, LocalizeFormattedNumber, LocalizeText } from '../../../api'; +import { GetConfigurationValue, localizeWithFallback, LocalizeFormattedNumber } from '../../../api'; import { Flex, Text } from '../../../common'; interface SeasonalViewProps { @@ -22,7 +22,7 @@ export const SeasonalView: FC = props => > - {LocalizeText(`purse.seasonal.currency.${type}`)} + {localizeWithFallback(`purse.seasonal.currency.${type}`, '')}