UNPKG

eleventy-i18n

Version:

i18n for Eleventy with dynamic parameters and pluralization support

140 lines (105 loc) 4.19 kB
const { areObjectsEqual, isEmptyObjectLiteral, isObjectLiteral, rejectProperties } = require('./utils.js'); const registry = require('./registry.js'); const defaultOptions = { keySeparator: undefined, fallbackLanguageTag: undefined, translations: {} }; module.exports = function (opts = {}) { opts = Object.assign({}, defaultOptions, opts); if (isEmptyObjectLiteral(opts.translations)) { throw new Error('no translations provided'); } for (const [languageTag, translations] of Object.entries(opts.translations)) { registry.addTranslations(languageTag, translations); } return (key, params = {}, languageTag) => { const fallbackTranslations = findFallbackLanguageTranslations(languageTag, opts); const translations = registry[languageTag]; let value; if (opts.keySeparator) { const splitKey = key.split(opts.keySeparator); const firstKey = splitKey.shift(); const translationsObject = (translations[firstKey] && translations) ?? (fallbackTranslations[firstKey] && fallbackTranslations) ?? (() => { throw new Error(`translation for "${firstKey}" not found`) })(); let o = translationsObject[firstKey]; while (splitKey.length > 0) { const k = splitKey.shift(); o = o[k] ?? (() => { throw new Error(`translation for "${key}" not found`) })(); } value = o; } else { value = translations[key] ?? fallbackTranslations[key] ?? (() => { throw new Error(`translation for "${key}" not found`) })(); } let paramsUsed = {}; let isPlural = false; if (isObjectLiteral(value)) { const pluralTranslations = value; const rejectedProps = rejectProperties(pluralTranslations, ['zero', 'one', 'two', 'few', 'many', 'other']); if (!isEmptyObjectLiteral(rejectedProps)) { throw new Error(`invalid keys "${JSON.stringify(rejectedProps)}"`); } const cardinal = params['cardinal']; const ordinal = params['ordinal']; if (!cardinal && !ordinal) { throw new Error('cardinal or ordinal parameter not found'); } if (cardinal && ordinal) { throw new Error('cannot use both cardinal and ordinal parameters'); } const count = cardinal || ordinal; const pluralRule = new Intl.PluralRules(languageTag).select(count); value = pluralTranslations[pluralRule]; if (!value) { throw new Error(`message for plural rule "${pluralRule}" not found`); } isPlural = true; } value = value.replace(/{{(.*?)}}/g, (_, path) => { let paramsUsed_ = paramsUsed; path = path.trim(); const splitKey = path.split('.'); const value = splitKey.reduce((params, key) => { paramsUsed_[key] = isObjectLiteral(params[key]) ? (paramsUsed_[key] ?? {}) : params[key]; paramsUsed_ = paramsUsed_[key]; return params[key] ?? (() => { throw new Error(`value for parameter "${path}" not found`) })(); }, params); if (typeof value !== 'string' && typeof value !== 'number') { (value === undefined) ? (() => { throw new Error(`value for parameter "${path}" not found`) })() : (() => { throw new Error(`invalid value for parameter "${path}": ${value}`) })(); } return value; }); if (isPlural) { if ('cardinal' in params) { paramsUsed.cardinal = params.cardinal; } if ('ordinal' in params) { paramsUsed.ordinal = params.ordinal; } } if (!areObjectsEqual(params, paramsUsed)) { throw new Error(`too many parameters passed "${JSON.stringify(params)}"`); } return value; }; }; function findFallbackLanguageTranslations(languageTag, opts) { if (!languageTag.includes('-')) { return registry[opts.fallbackLanguageTag]; } const fallbackLanguageTag = languageTag.split('-')[0]; return registry[fallbackLanguageTag] ?? registry[opts.fallbackLanguageTag] ?? {}; }