All Downloads are FREE. Search and download functionalities are using the official Maven repository.

helpers.translator.ts Maven / Gradle / Ivy

import i18next from 'i18next';

import { TRANSLATION } from './environment';
import { F } from './formatter';

interface TranslateOptions {
  strict   : boolean;
  domain   : string | undefined;
  user     : boolean;
  fallback : string | undefined;
  variables: object;
  format   : FormatFunction | undefined;
}

export interface PartialTranslateOptions extends Partial {}

export type TranslateKey = string[] | string | undefined

export type FormatFunction = (str: string) => string
export type TranslateFunction = (key: TranslateKey, options?: PartialTranslateOptions) => string | undefined

class Translator {
  t             : Function;
  i18n          : typeof i18next;

  static appDomain    : string = 'app'
  static defaultDomain: string = 'default'

  constructor(t: Function, i18n: typeof i18next) {
    this.t = t
    this.i18n = i18n
  }

  translate: TranslateFunction = (key: TranslateKey, options: PartialTranslateOptions = defaultOptions): string | undefined => {
    return this.apply(key, {...defaultOptions, ...options})
  }
   
  // the main translation function, a.k.a. where the `magic` happens
  private apply(key: TranslateKey, options: TranslateOptions): string | undefined {
    // return the found key translation or use the fallback
   
    // get the translation
    const { keys, found, translation } = this.getTranslation(key, options)

    // log if it is missing
    if (translation == undefined && options.strict)
      console.warn("No translation found for keys:\n%s", Translator.toKeys(keys).join("\n"))

    // format the translation result
    return Translator.formatTranslation(translation, {found, keys, options})       
  }

  static toKeys(keys: TranslateKey): string[] {
    return keys == undefined ? [] : Array.isArray(keys) ? keys : [keys]
  }

  static addDomain(domain: string, key: string): string {
    return `${domain}:${key}`
  }
 
  private getTranslation(key: TranslateKey, options: TranslateOptions) {
    const domain = options.domain || options.user && Translator.appDomain || undefined

    const appKeys   = domain
      ? Translator.toKeys(key).map(key => Translator.addDomain(domain as string, key))
      : Translator.toKeys(key)

    // for every key in the app domain, create a key in the default domain 
    const defaultKeys = TRANSLATION != "missing" && TRANSLATION != "all"
      ? appKeys
        .filter(key => key.startsWith(Translator.appDomain))
        .map(key => Translator.addDomain(Translator.defaultDomain, key.substring(Translator.appDomain.length + 1)))
      : []

    const keys        = [...appKeys, ...defaultKeys]
    const found       = Translator.toKeys(keys).find(key => this.i18n.exists(key))
    const translation = found && this.t(found, options.variables) || options.fallback

    return { keys, found, translation }
  }

  // return the formatted translation, or the used key. Influenced by TRANSLATION.
  private static formatTranslation(
    translation: string | undefined, 
    { found, keys, options } : {
      found: string | undefined, 
      keys: string[], 
      options: TranslateOptions
    }
  ) {
      const getKeyString = () => {
        const keyStr = keys.length > 0 ? keys[0] : "missing key"
        if (TRANSLATION == 'all')
          return `<<${keyStr}>>` 
        else if (found == undefined)
          return `<<${keyStr} (missing)>>`
        else 
          return `<<${found} (found)>>` 
      } 

      const format = () => {
        if (typeof translation == 'string')
          return options.format ? options.format(translation) : translation 
        else
          return options.strict || TRANSLATION != "normal" ? getKeyString() : translation
      }

      switch (TRANSLATION) {
        case 'missing':
          if (found == undefined)
            return getKeyString()
          else
            return format()
  
        case 'found':
          if (found != undefined)
            return getKeyString()
          else 
            return format()
  
        case 'all':
          return getKeyString()
  
        default:
          return format()
      }
    }
}

class GearsTranslator extends Translator {
    /* key transformations */

  private static asDetailLabelKey(detailKey: string, key: string): string   { return `views.details.${detailKey}.labels.${key}` }

  /* domain transformations */
  static inAppDomain(key: string): string { return Translator.addDomain(G.appDomain, key) }

  toValue = (value: any, label?: string, processKey?: string) => {
    const getValue = () => {
      const valueStr: string = typeof value != "string" ? JSON.stringify(value) : value
      const valueKey = valueStr.toLocaleLowerCase()

      return this.translate([
        G.inAppDomain(`processes.${processKey}.values.${valueKey}`),
        G.inAppDomain(`common.values.${valueKey}`),
        `values.${valueKey}`
      ], { fallback: label || F.toSentenceCase(valueKey.replaceAll("_", " ")) })
    }

    const val = getValue()
    return val && F.toBraceless(val) || val
  }

  /* specific translations with fallback behavior */
  toMenuLabel = (menuKey: string, label?: string) => this.translate([
    G.inAppDomain(`menu.${menuKey}`),
    `menu.${menuKey}`
  ], {fallback: label, format: F.toSentenceCase})

  toProjectName        = (project: string)    => this.translate(
    `common.project_name`,
    {user: true, fallback: project, format: F.toTitleCase}
  )

  toProcessTitle       = (processKey: string, label?: string, language?: string) => this.translate(
    `processes.${processKey}.title`,
    {user: true, fallback: label, format: language == "nl" ? F.toSentenceCase : F.toTitleCase}
  )

  toProcessDescription = (processKey: string, label?: string) => this.translate(
    `processes.${processKey}.description`,
    {user: true, fallback: label, format: F.toMultiSentenceCase, strict: false}
  )

  toListTitle          = (listKey: string, label?: string)    => this.translate(
    `views.lists.${listKey}.title`,
    {user: true, fallback: label, format: F.toTitleCase}
  )

  toListShortTitle     = (listKey: string, label?: string)    => this.translate([
    `views.lists.${listKey}.short_title`,
    `views.lists.${listKey}.title`
  ],{user: true, fallback: label, format: F.toSentenceCase})

  toListMenu           = (listKey: string, label?: string) => this.translate([
    `views.lists.${listKey}.menu`,
    `views.common.menu`,
    `common.menu`
  ], {user: true, fallback: label, format: F.toSentenceCase})

  toDetailTitle        = (detailKey: string, label?: string)  => this.translate(
    `views.details.${detailKey}.title`,
    {user: true, fallback: label, format: F.toTitleCase}
  )!

  toDetailShortTitle   = (detailKey: string, label?: string)  => this.translate([
    `views.details.${detailKey}.short_title`,
    `views.details.${detailKey}.title`
  ], {user: true, fallback: label, format: F.toSentenceCase})

  toDetailTableKey = (detailKey: string, keyFallback?: string) => {
    const tableKeyFallback   = this.t("group.table_key")
    return this.translate(G.asDetailLabelKey(detailKey, 'table_key'),{fallback: keyFallback || tableKeyFallback, user: true, format: F.toSentenceCase} )
  }

  toDetailTableValue = (detailKey: string, valueFallback?: string) => {
    const tableValueFallback = this.t("group.table_value")
    return this.translate(G.asDetailLabelKey(detailKey, 'table_value'),{fallback: valueFallback || tableValueFallback, user: true, format: F.toSentenceCase} )
  }

  toTaskTitle          = (processKey: string, formNr: number, options?: PartialTranslateOptions) => this.translate(
    `processes.${processKey}.tasks.${formNr}.title`,
    {...options, user: true, format: F.toSentenceCase}
  )

  toTaskDescription    = (processKey: string, formNr: number, options?: PartialTranslateOptions) => this.translate(
    `processes.${processKey}.tasks.${formNr}.description`,
    {...options, user: true, format: F.toMultiSentenceCase}
  )

  toTaskShortTitle     = (processKey: string, formNr: number, options?: PartialTranslateOptions) => this.translate(
    `processes.${processKey}.tasks.${formNr}.short_title`,
    { fallback: this.t("task") + " " + formNr, ...options, user: true, format: F.toSentenceCase}
  )

  toCategory = (key: string) => this.translate(
    `common.categories.${key}`,
    {user: true, fallback: key, format: F.toSentenceCase}
  )

  static toErrorKeys = (processKey: string, key: string): string[] => {
    const keyLabel = key.toLowerCase()
    return [
      `processes.${processKey}.errors.${keyLabel}`,
      `common.errors.${keyLabel}`
    ]
  }

  toError = (processKey: string, key: string, fallback?: string) => this.translate(
    G.toErrorKeys(processKey, key),
    {user: true, fallback: fallback, strict: false, format: F.toSentenceCase}
  )

  static toLabelKeys = (processKey: string, key: string): string[] => {
    const keyLabel = key.toLowerCase()
    return [
      `processes.${processKey}.labels.${keyLabel}`,
      `common.labels.${keyLabel}`
    ]
  }
  
  toLabel = (processKey: string, key: string, label?: string) => this.translate(
    G.toLabelKeys(processKey, key),
    {user: true, fallback: label, strict: false, format: F.toSentenceCase}
  )

  toListLabel = (listKey: string, key: string, label?: string) => {
    const keyLabel = key.toLowerCase()
    return this.translate([
      `views.lists.${listKey}.labels.${keyLabel}`,
      `views.common.${listKey}.labels.${keyLabel}`,
      `common.labels.${keyLabel}`,
    ], {user: true, fallback: label, format: F.toSentenceCase})
  }

  toDetailLabel = (detailKey: string, key: string, label?: string) => {
    const keyLabel = key.toLowerCase()
    return this.translate([
      `views.details.${detailKey}.labels.${keyLabel}`,
      `views.common.${detailKey}.labels.${keyLabel}`,
      `common.labels.${keyLabel}`,
    ], {user: true, fallback: label, format: F.toSentenceCase})
  }

  toListLink = (listKey: string, link: string, label?: string) => this.translate([
    `views.lists.${listKey}.links.${link}`,
    `views.common.links.${link}`,
    `common.links.${link}`,
  ], {user: true, fallback: label, format: F.toSentenceCase})

  toDetailLink = (detailKey: string, link: string, label?: string) => this.translate([
    `views.details.${detailKey}.links.${link}`,
    `views.common.links.${link}`,
    `common.links.${link}`,
  ], {user: true, fallback: label, format: F.toSentenceCase})

  toActorName = (processKey: string, actor: string, label?: string) => this.translate([
    `processes.${processKey}.actors.${actor}`,
    `common.actors.${actor}`
  ], { user: true, fallback: label || actor, strict: false, format: F.toTitleCase})

}

const G: typeof GearsTranslator = GearsTranslator

const defaultOptions: TranslateOptions = {
  strict   : false,
  domain   : undefined,
  user     : false,
  fallback : undefined,
  variables: {},
  format   : (str: string) => str.replace(/[{}]/g, '')
}

export {GearsTranslator as default, GearsTranslator as T}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy