UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

300 lines (299 loc) 9.47 kB
"use client"; var _br; import { Fragment, useCallback, useContext, useMemo, useRef } from 'react'; import Context from "./Context.js"; import defaultLocales from "./locales/index.js"; import { isObject, warn } from "./component-helper.js"; import { LOCALE } from "./defaults.js"; import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; export default function useTranslation(messages, args) { const { locale, translation, messageFormatter } = useContext(Context); const { translations: contextTranslations } = useContext(Context); const { assignUtils } = useAdditionalUtils(messageFormatter); const { extMessages, fallbackLocale, baseOverride, warnLabel } = useMemo(() => { var _arg$fallbackLocale; const defaultLocaleKeys = Object.keys(defaultLocales); const defaultLocale = defaultLocaleKeys[0]; const arg = isObject(messages) && ('messages' in messages || 'fallbackLocale' in messages) ? messages : { messages }; return { extMessages: arg.messages, fallbackLocale: (_arg$fallbackLocale = arg.fallbackLocale) !== null && _arg$fallbackLocale !== void 0 ? _arg$fallbackLocale : defaultLocale, baseOverride: arg.base, warnLabel: arg.warnLabel || 'useTranslation' }; }, [messages]); return useMemo(() => { var _ref; const id = typeof messages === 'string' ? messages : undefined; if (id) { return formatMessage(id, args, translation); } let translationLocale = locale; if (locale.startsWith('en-') && !Object.keys(defaultLocales).some(l => l === locale)) { translationLocale = 'en-GB'; } const base = assignUtils(combineWithExternalTranslations({ translation: baseOverride || translation, messages: extMessages, locale: translationLocale }), translationLocale); if (!fallbackLocale) { return base; } const explicitMessages = extMessages; let hasExplicitCurrent = false; let currentMessages = undefined; if (explicitMessages && Object.hasOwn(explicitMessages, locale)) { hasExplicitCurrent = true; currentMessages = explicitMessages[locale]; } else if (contextTranslations && Object.hasOwn(contextTranslations, locale)) { hasExplicitCurrent = true; currentMessages = contextTranslations[locale]; } const fallbackMessages = (_ref = explicitMessages?.[fallbackLocale] || contextTranslations?.[fallbackLocale] || defaultLocales[fallbackLocale]) !== null && _ref !== void 0 ? _ref : undefined; if (!fallbackMessages || !hasExplicitCurrent) { return base; } const currentHasContent = isObject(currentMessages) && Object.keys(currentMessages).length > 0; if (!currentHasContent) { warnMissing(locale, warnLabel); const obj = generateTranslationKeyReferences('', fallbackMessages); return withUtils(base, obj); } const { result, hasMissing } = mergeMissingKeys(base, fallbackMessages); if (hasMissing) { warnMissing(locale, warnLabel); return withUtils(base, result); } return base; }, [messages, locale, assignUtils, baseOverride, translation, extMessages, fallbackLocale, contextTranslations, args, warnLabel]); } function withUtils(base, obj) { return Object.assign({}, base, obj, { formatMessage: base.formatMessage, renderMessage: base.renderMessage, countries: base.countries }); } function mergeMissingKeys(target, source) { const resultLocal = { ...target }; let hasMissing = false; const keys = Object.keys(source); for (const key of keys) { const sourceValue = source[key]; const targetValue = resultLocal[key]; if (isObject(sourceValue)) { if (!targetValue) { resultLocal[key] = generateTranslationKeyReferences(key, sourceValue); hasMissing = true; } else if (isObject(targetValue)) { const nested = mergeMissingKeys(targetValue, sourceValue); resultLocal[key] = nested.result; if (nested.hasMissing) { hasMissing = true; } } } else if (targetValue === undefined) { resultLocal[key] = key; hasMissing = true; } } return { result: resultLocal, hasMissing }; } function generateTranslationKeyReferences(baseKey, sourceValue) { if (!isObject(sourceValue)) { return baseKey ? baseKey : sourceValue; } const result = {}; const entries = Object.entries(sourceValue); for (const [key, value] of entries) { const translationKey = baseKey ? `${baseKey}.${key}` : key; result[key] = isObject(value) ? generateTranslationKeyReferences(translationKey, value) : translationKey; } return result; } function warnMissing(locale, label = 'useTranslation') { warn(`${label}: No translations found for locale "${locale}"!`); } export function useAdditionalUtils(messageFormatter) { const translationsRef = useRef(undefined); const localeRef = useRef(LOCALE); const messageFormatterRef = useRef(messageFormatter); messageFormatterRef.current = messageFormatter; const fM = useCallback((id, args) => { return formatMessage(id, args, translationsRef.current, localeRef.current, messageFormatterRef.current); }, []); const rM = useCallback(message => { return renderMessage(message); }, []); const assignUtils = useCallback((translations, locale) => { translationsRef.current = translations; if (locale) { localeRef.current = locale; } Object.assign(translations, { formatMessage: fM, renderMessage: rM }); return translations; }, [fM, rM]); return { assignUtils }; } export function combineWithExternalTranslations({ translation, messages, locale }) { let combined = { ...translation }; if (messages) { if (Object.keys(defaultLocales).some(locale => messages[locale])) { if (messages[locale]) { combined = messages[locale]; } } for (const key in messages) { combined[key] = { ...translation[key], ...messages[key] }; } } return combined; } export function formatMessage(id, args, messages, locale, messageFormatter) { let str = undefined; if (typeof id === 'string') { let found = false; if (messages[id]) { str = messages[id]; found = true; } else if (id?.includes?.('.')) { const keys = id.split('.'); for (const key of keys) { if (messages[key]) { messages = messages[key]; } else { break; } } if (typeof messages === 'string') { str = messages; found = true; } } if (!found && typeof id === 'string') { if (id.includes('.')) { warn(`formatMessage: Could not resolve translation key "${id}". It may be missing or incorrectly formatted.`); } str = id; } } else if (typeof id === 'function') { str = id(messages); } if (typeof str === 'string') { if (args && messageFormatter && messageFormatter.isICU(str)) { try { const result = messageFormatter.format(str, args, locale || LOCALE); if (Array.isArray(result)) { return _jsx(_Fragment, { children: result.map((part, i) => _jsx(Fragment, { children: part }, i)) }); } return result; } catch (e) { warn(`formatMessage: ICU formatting failed for "${typeof id === 'string' ? id : '(function)'}":`, e); return typeof id === 'string' ? id : str; } } let hasTagHandlers = false; for (const t in args) { if (typeof args[t] === 'function' && str.includes(`<${t}>`)) { hasTagHandlers = true; continue; } const value = typeof args[t] === 'function' ? args[t]() : args[t]; const regex = new RegExp(`{${t}}`, 'g'); str = str.replace(regex, value); } const unreplaced = str.match(/\{(\w+)\}/g)?.filter(p => p !== '{br}'); if (unreplaced?.length > 0) { warn(`formatMessage: Unreplaced placeholder(s) ${unreplaced.join(', ')} in "${typeof id === 'string' ? id : '(function)'}".`); } if (hasTagHandlers) { return renderTags(str, args); } } return str !== null && str !== void 0 ? str : id; } export function renderMessage(text) { let element = text; if (typeof text === 'string') { element = text.split('{br}'); } if (Array.isArray(element)) { return element.map((item, index) => _jsxs(Fragment, { children: [item, _br || (_br = _jsx("br", {}))] }, index)); } return text; } function renderTags(str, args) { const TAG_RE = /<(\w+)>([\s\S]*?)<\/\1>/; const parts = []; let remaining = str; let match; while ((match = TAG_RE.exec(remaining)) !== null) { const [fullMatch, tagName, content] = match; if (match.index > 0) { parts.push(remaining.slice(0, match.index)); } const handler = args[tagName]; if (typeof handler === 'function') { parts.push(handler(content)); } else { parts.push(fullMatch); } remaining = remaining.slice(match.index + fullMatch.length); } if (remaining) { parts.push(remaining); } if (parts.length === 1 && typeof parts[0] === 'string') { return parts[0]; } return _jsx(_Fragment, { children: parts.map((part, i) => _jsx(Fragment, { children: part }, i)) }); } //# sourceMappingURL=useTranslation.js.map