@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
304 lines (303 loc) • 10.2 kB
JavaScript
"use client";
var _br;
import _Object$hasOwn from "core-js-pure/stable/object/has-own.js";
import _pushInstanceProperty from "core-js-pure/stable/instance/push.js";
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 === null || explicitMessages === void 0 ? void 0 : explicitMessages[fallbackLocale]) || (contextTranslations === null || contextTranslations === void 0 ? void 0 : 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') {
var _id$includes;
let found = false;
if (messages[id]) {
str = messages[id];
found = true;
} else if (id !== null && id !== void 0 && (_id$includes = id.includes) !== null && _id$includes !== void 0 && _id$includes.call(id, '.')) {
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') {
var _str$match;
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 = str.match(/\{(\w+)\}/g)) === null || _str$match === void 0 ? void 0 : _str$match.filter(p => p !== '{br}');
if ((unreplaced === null || unreplaced === void 0 ? void 0 : 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) {
_pushInstanceProperty(parts).call(parts, remaining.slice(0, match.index));
}
const handler = args[tagName];
if (typeof handler === 'function') {
_pushInstanceProperty(parts).call(parts, handler(content));
} else {
_pushInstanceProperty(parts).call(parts, fullMatch);
}
remaining = remaining.slice(match.index + fullMatch.length);
}
if (remaining) {
_pushInstanceProperty(parts).call(parts, 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