translation.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import { insertValues } from "./misc.js";
  2. import type { Stringifiable } from "./types.js";
  3. /**
  4. * Translation object to pass to {@linkcode tr.addLanguage()}
  5. * Can be a flat object of identifier keys and the translation text as the value, or an infinitely nestable object containing the same.
  6. *
  7. * @example
  8. * // Flat object:
  9. * const tr_en: TrObject = {
  10. * hello: "Hello, %1!",
  11. * foo: "Foo",
  12. * };
  13. *
  14. * // Nested object:
  15. * const tr_de: TrObject = {
  16. * hello: "Hallo, %1!",
  17. * foo: {
  18. * bar: "Foo bar",
  19. * },
  20. * };
  21. */
  22. export interface TrObject {
  23. [key: string]: string | TrObject;
  24. }
  25. /** All translations and some metadata */
  26. const trans: {
  27. [language: string]: TrObject;
  28. } = {};
  29. /** Currently set language */
  30. let curLang = "";
  31. /** Common function to resolve the translation text in a specific language. */
  32. function translate(language: string, key: string, ...args: Stringifiable[]): string {
  33. const trObj = trans[language]?.data;
  34. if(typeof language !== "string" || typeof trObj !== "object" || trObj === null)
  35. return key;
  36. // try to resolve via traversal (e.g. `trObj["key"]["parts"]`)
  37. const keyParts = key.split(".");
  38. let value: string | TrObject | undefined = trObj;
  39. for(const part of keyParts) {
  40. if(typeof value !== "object" || value === null)
  41. break;
  42. value = value?.[part];
  43. }
  44. if(typeof value === "string")
  45. return insertValues(value, args);
  46. // try falling back to `trObj["key.parts"]`
  47. value = trObj?.[key];
  48. if(typeof value === "string")
  49. return insertValues(value, args);
  50. // default to translation key
  51. return key;
  52. }
  53. /**
  54. * Returns the translated text for the specified key in the current language set by {@linkcode tr.setLanguage()}
  55. * Use {@linkcode tr.forLang()} to get the translation for a specific language instead of the currently set one.
  56. * If the key is not found in the currently set language, the key itself is returned.
  57. *
  58. * ⚠️ Remember to register a language with {@linkcode tr.addLanguage()} and set it as active with {@linkcode tr.setLanguage()} before using this function, otherwise it will always return the key itself.
  59. * @param key Key of the translation to return
  60. * @param args Optional arguments to be passed to the translated text. They will replace placeholders in the format `%n`, where `n` is the 1-indexed argument number
  61. */
  62. const tr = (key: string, ...args: Stringifiable[]): string => translate(curLang, key, ...args);
  63. /**
  64. * Returns the translated text for the specified key in the specified language.
  65. * If the key is not found in the specified previously registered translation, the key itself is returned.
  66. *
  67. * ⚠️ Remember to register a language with {@linkcode tr.addLanguage()} before using this function, otherwise it will always return the key itself.
  68. * @param language Language to use for the translation
  69. * @param key Key of the translation to return
  70. * @param args Optional arguments to be passed to the translated text. They will replace placeholders in the format `%n`, where `n` is the 1-indexed argument number
  71. */
  72. tr.forLang = translate;
  73. /**
  74. * Registers a new language with its translations.
  75. * The translations are a key-value pair where the key is the translation key and the value is the translated text.
  76. *
  77. * The translations can contain placeholders in the format `%n`, where `n` is the 1-indexed argument number.
  78. * These placeholders will be replaced by the arguments passed to the translation functions.
  79. * @param language Language code to register
  80. * @param translations Translations for the specified language
  81. * @example ```ts
  82. * tr.addLanguage("en", {
  83. * hello: "Hello, %1!",
  84. * foo: {
  85. * bar: "Foo bar",
  86. * },
  87. * });
  88. * ```
  89. */
  90. tr.addLanguage = (language: string, translations: TrObject): void => {
  91. trans[language] = translations;
  92. };
  93. /**
  94. * Sets the active language for the translation functions.
  95. * This language will be used by the {@linkcode tr()} function to return the translated text.
  96. * If the language is not registered with {@linkcode tr.addLanguage()}, the translation functions will always return the key itself.
  97. * @param language Language code to set as active
  98. */
  99. tr.setLanguage = (language: string): void => {
  100. curLang = language;
  101. };
  102. /**
  103. * Returns the active language set by {@linkcode tr.setLanguage()}
  104. * If no language is set, this function will return `undefined`.
  105. * @returns Active language code
  106. */
  107. tr.getLanguage = (): string => {
  108. return curLang;
  109. };
  110. /**
  111. * Returns the translations for the specified language or currently active one.
  112. * If the language is not registered with {@linkcode tr.addLanguage()}, this function will return `undefined`.
  113. * @param language Language code to get translations for - defaults to the currently active language (set by {@linkcode tr.setLanguage()})
  114. * @returns Translations for the specified language
  115. */
  116. tr.getTranslations = (language?: string): TrObject | undefined => {
  117. return trans[language ?? curLang];
  118. };
  119. export { tr };