{"version":3,"file":"translator.mjs","names":[],"sources":["../../../../../../localization/src/translator.ts"],"sourcesContent":["import { flatten, get, merge, set } from \"@mongez/reinforcements\";\r\nimport { getLocalizationConfigurations } from \"./config\";\r\nimport { plainConverter } from \"./converters\";\r\nimport { formatCount, getCountKey } from \"./count-rules\";\r\nimport { localizationEvents } from \"./events\";\r\nimport { getPlaceholderPattern } from \"./placeholder-pattern-config\";\r\nimport {\r\n  Converter,\r\n  GroupedTranslations,\r\n  Keywords,\r\n  Translatable,\r\n  TranslationsList,\r\n} from \"./types\";\r\n\r\n/**\r\n * Current locale code\r\n */\r\nlet currentLocaleCode: string = \"en\";\r\n\r\n/**\r\n * Current converter\r\n */\r\nlet currentConverter: Converter = plainConverter;\r\n\r\n/**\r\n * all keywords for all locale codes\r\n */\r\nlet translationsList: TranslationsList = {};\r\n\r\n/**\r\n * Current fall back locale code\r\n */\r\nlet fallbackLocaleCode: string = \"en\";\r\n\r\n/**\r\n * Get current locale code\r\n */\r\nexport function getCurrentLocaleCode() {\r\n  return currentLocaleCode;\r\n}\r\n\r\n/**\r\n * Get fallback locale code\r\n */\r\nexport function getFallbackLocaleCode() {\r\n  return fallbackLocaleCode;\r\n}\r\n\r\n/**\r\n * Set current converter\r\n */\r\nexport function setConverter(converter: Converter) {\r\n  currentConverter = converter;\r\n}\r\n\r\n/**\r\n * Get the locale code used in translation, this allows to get the locale code on the fly\r\n */\r\nexport function getTranslationLocaleCode(): string {\r\n  const config = getLocalizationConfigurations();\r\n  // Prefer the correctly-spelled key; fall back to the legacy misspelling for backward compat.\r\n  return (\r\n    config.translationLocaleCode ||\r\n    config.translationLocalCode ||\r\n    currentLocaleCode\r\n  );\r\n}\r\n\r\n/**\r\n * Set current locale code\r\n */\r\nexport function setCurrentLocaleCode(localeCode: string): void {\r\n  const oldLocaleCode = currentLocaleCode;\r\n  currentLocaleCode = localeCode;\r\n  localizationEvents.triggerChange(\"localeCode\", localeCode, oldLocaleCode);\r\n}\r\n\r\n/**\r\n * Add keywords\r\n */\r\nexport function extend(localeCode: string, keywords: Keywords) {\r\n  translationsList[localeCode] = merge(\r\n    translationsList[localeCode] || {},\r\n    keywords,\r\n  ) as Keywords;\r\n}\r\n\r\n/**\r\n * Create a grouped translations based on keyword, each keyword contains list of locale codes and beside it its corresponding translation\r\n *\r\n * @example\r\n * {\r\n *  home: {\r\n *    en: \"Home\",\r\n *    ar: \"الرئيسية\"\r\n *  }\r\n * }\r\n *\r\n * Also it could have nested grouped translations\r\n *\r\n * @example\r\n * {\r\n *  general: {\r\n *    home: {\r\n *      en: \"Home\",\r\n *      ar: \"الرئيسية\"\r\n *    }\r\n *  }\r\n */\r\nexport function groupedTranslations(\r\n  groupKey?: string | GroupedTranslations,\r\n  groupedTranslations?: GroupedTranslations,\r\n): void {\r\n  if (typeof groupKey !== \"string\" && !groupedTranslations) {\r\n    groupedTranslations = groupKey;\r\n    groupKey = undefined;\r\n  }\r\n\r\n  // now we need to loop over the grouped translations\r\n  // we have two cases here\r\n  // first one we have a group key\r\n  // second one we don't have a group key\r\n  // as values are always objects until the last level\r\n  // we need to create a recursive function to loop over the object\r\n\r\n  // output of the flatten object will be something like:\r\n  // general.home.en: \"Home\"\r\n  // general.home.ar: \"الرئيسية\"\r\n  const object = flatten(\r\n    groupKey && typeof groupKey === \"string\"\r\n      ? { [groupKey]: groupedTranslations }\r\n      : groupedTranslations,\r\n  );\r\n\r\n  // now the locale codes are the last dot in each key\r\n  // now we will loop over the object and add each key to the translations list\r\n  for (const key in object) {\r\n    const keyword = key.split(\".\");\r\n    const localeCode = keyword.pop();\r\n\r\n    set(translationsList, localeCode + \".\" + keyword.join(\".\"), object[key]);\r\n  }\r\n}\r\n\r\n/**\r\n * Override the entire translations list\r\n */\r\nexport function setTranslationsList(translations: TranslationsList): void {\r\n  translationsList = translations;\r\n}\r\n\r\n/**\r\n * Get the entire translations list\r\n */\r\nexport function getTranslationsList(): TranslationsList {\r\n  return translationsList;\r\n}\r\n\r\n/**\r\n * Get the keywords list of the given locale code\r\n */\r\nexport function getKeywordsListOf(localeCode: string): Keywords | null {\r\n  return translationsList[localeCode] || null;\r\n}\r\n\r\n/**\r\n * Set fallback locale code, if the keyword does not exist on current locale code,\r\n * then check it in the faLLBACK locale code instead\r\n */\r\nexport function setFallbackLocaleCode(fallbackLocale: string) {\r\n  const oldFallback = fallbackLocaleCode;\r\n\r\n  fallbackLocaleCode = fallbackLocale;\r\n  localizationEvents.triggerChange(\"fallback\", fallbackLocale, oldFallback);\r\n}\r\n\r\n/**\r\n * Translate the given keyword in current locale code\r\n */\r\nexport function trans(\r\n  keyword: Translatable,\r\n  placeholders?: any,\r\n  converter: Converter = currentConverter,\r\n) {\r\n  return transFrom(\r\n    getTranslationLocaleCode(),\r\n    keyword,\r\n    placeholders,\r\n    converter,\r\n  );\r\n}\r\n\r\n/**\r\n * Translate using the default converter\r\n */\r\nexport function plainTrans(keyword: string, placeholders?: any) {\r\n  return transFrom(\r\n    getTranslationLocaleCode(),\r\n    keyword,\r\n    placeholders,\r\n    plainConverter,\r\n  );\r\n}\r\n\r\n/**\r\n * Translate the given keyword for the given locale code\r\n * Please note this method accepts dot notation syntax\r\n */\r\nexport function transFrom(\r\n  localeCode: string,\r\n  keyword: Translatable,\r\n  placeholders?: any,\r\n  converter = currentConverter,\r\n) {\r\n  let translation;\r\n  if (typeof keyword === \"object\") {\r\n    translation = keyword[localeCode] || keyword[fallbackLocaleCode];\r\n  } else {\r\n    // Check if we have a count in placeholders\r\n    if (placeholders?.count !== undefined) {\r\n      const count = Number(placeholders.count);\r\n      \r\n      // Try current locale with its count rules\r\n      const currentCountKey = getCountKey(count, localeCode);\r\n      translation = get(translationsList, `${localeCode}.${keyword}${currentCountKey}`);\r\n      \r\n      // If not found and we have a fallback, try the fallback locale with its own count rules\r\n      if (!translation && fallbackLocaleCode) {\r\n        const fallbackCountKey = getCountKey(count, fallbackLocaleCode);\r\n        translation = get(translationsList, `${fallbackLocaleCode}.${keyword}${fallbackCountKey}`);\r\n      }\r\n      \r\n      // If still not found, try other variants in order:\r\n      // 1. Current locale _other\r\n      // 2. Fallback locale _other\r\n      // 3. Current locale base\r\n      // 4. Fallback locale base\r\n      if (!translation) {\r\n        translation = get(translationsList, `${localeCode}.${keyword}_other`) ||\r\n          (fallbackLocaleCode && get(translationsList, `${fallbackLocaleCode}.${keyword}_other`)) ||\r\n          get(translationsList, `${localeCode}.${keyword}`) ||\r\n          (fallbackLocaleCode && get(translationsList, `${fallbackLocaleCode}.${keyword}`));\r\n      }\r\n\r\n      // Format the count value according to configuration\r\n      if (translation && placeholders.count !== undefined) {\r\n        placeholders = { ...placeholders, count: formatCount(count) };\r\n      }\r\n    } else {\r\n      // No count, just get regular translation\r\n      translation =\r\n        get(translationsList, `${localeCode}.${keyword}`) ||\r\n        (fallbackLocaleCode\r\n          ? get(translationsList, `${fallbackLocaleCode}.${keyword}`)\r\n          : null);\r\n    }\r\n  }\r\n\r\n  if (!translation) return keyword;\r\n\r\n  return placeholders\r\n    ? converter(translation, placeholders, getPlaceholderPattern())\r\n    : translation;\r\n}\r\n\r\nexport type WithPlaceholder<T> = {\r\n  p: (keyword: keyof T, placeholders?: any) => string;\r\n  plain: (keyword: keyof T, placeholders?: any) => string;\r\n};\r\n\r\n/**\r\n * Get a translation object with automatic translation using object syntax\r\n * Please note this does not support nested objects, only keywords and their translations\r\n * i.e\r\n * const translations = transObject({\r\n *  name: {\r\n *     en: 'name',\r\n *    ar: 'الاسم'\r\n * }\r\n * });\r\n *\r\n * Usage: translations.name // returns the name in current locale code\r\n * If the keyword does not exist on current locale code, then it will check it in the fallback locale code\r\n *\r\n * If keyword is \"p\", then it will return a function that accepts keyword and its placeholders\r\n * If keyword is \"plain\", then the converter used in translation will be the plain converter\r\n */\r\nexport function transObject<T extends Keywords>(translations: T) {\r\n  // use proxy\r\n  return new Proxy(translations as any, {\r\n    get(target, key: string) {\r\n      if (key === \"p\") {\r\n        return function (keyword: keyof T, placeholders?: any) {\r\n          return transFrom(\r\n            currentLocaleCode,\r\n            target[keyword] as Translatable,\r\n            placeholders,\r\n            currentConverter,\r\n          );\r\n        };\r\n      }\r\n\r\n      if (key === \"plain\") {\r\n        return function (keyword: keyof T, placeholders?: any) {\r\n          return transFrom(\r\n            currentLocaleCode,\r\n            target[keyword] as Translatable,\r\n            placeholders,\r\n            plainConverter,\r\n          );\r\n        };\r\n      }\r\n\r\n      if (!target[key]) return transFrom(fallbackLocaleCode, key as string);\r\n\r\n      return transFrom(currentLocaleCode, target[key] as Translatable);\r\n    },\r\n  }) as WithPlaceholder<T> & {\r\n    [key in keyof T]: string;\r\n  };\r\n}\r\n"],"mappings":"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"}