All files / lib/services i18n.service.ts

95.56% Statements 43/45
89.29% Branches 25/28
100% Functions 4/4
95.35% Lines 41/43

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 1136x 6x 6x             6x 6x 6x     6x     10x   10x   10x 10x                   67x         67x   67x   67x           10x 4x     10x 3x 3x   3x             64x   64x 3x         64x       1x       4x 4x         4x 4x 4x   4x     4x 4x 2x 2x         4x 1x       3x   3x 3x          
import { Inject, Injectable, Logger } from '@nestjs/common';
import * as format from 'string-format';
import {
  I18N_OPTIONS,
  I18N_TRANSLATIONS,
  I18nTranslation,
  I18N_LANGUAGES,
} from '../i18n.constants';
import { I18nOptions } from '..';
import * as fs from 'fs';
import * as _ from 'lodash';
import * as path from 'path';
 
@Injectable()
export class I18nService {
  constructor(
    @Inject(I18N_OPTIONS)
    private readonly i18nOptions: I18nOptions,
    @Inject(I18N_TRANSLATIONS)
    private readonly translations: I18nTranslation,
    @Inject(I18N_LANGUAGES)
    private readonly supportedLanguages: string[],
    private readonly logger: Logger,
  ) {}
 
  public translate(
    key: string,
    options?: {
      lang?: string;
      args?: Array<{ [k: string]: any } | string> | { [k: string]: any };
    },
  ) {
    options = {
      lang: this.i18nOptions.fallbackLanguage,
      ...options,
    };
 
    const { lang, args } = options;
 
    const translationsByLanguage = this.translations[lang];
 
    if (
      translationsByLanguage === undefined ||
      translationsByLanguage === null ||
      (!!translationsByLanguage && !translationsByLanguage.hasOwnProperty(key))
    ) {
      // and now we detect, if this should also be added to the MISSING file
      if (this.i18nOptions.saveMissing === true) {
        this.saveMissingTranslation(key, lang);
      }
 
      if (lang !== this.i18nOptions.fallbackLanguage) {
        const message = `Translation "${key}" in "${lang}" does not exist.`;
        this.logger.error(message);
 
        return this.translate(key, {
          lang: this.i18nOptions.fallbackLanguage,
          args: args,
        });
      }
    }
 
    let translation = translationsByLanguage[key];
 
    if (translation && (args || (args instanceof Array && args.length > 0))) {
      translation = format(
        translation,
        ...(args instanceof Array ? args || [] : [args]),
      );
    }
    return translation || key;
  }
 
  public getSupportedLanguages() {
    return this.supportedLanguages;
  }
 
  private saveMissingTranslation(key: string, language: string) {
    const filePathMissing = path.join(this.i18nOptions.path, language);
    Iif (!fs.existsSync(filePathMissing)) {
      this.logger.error(`Cannot find path to store missing translations`);
      return;
    }
 
    const keyParts = key.split('.');
    const filePart = keyParts.shift();
    const keyWithoutFile = keyParts.join('.');
 
    const filePath = path.join(filePathMissing, `${filePart}.missing`);
 
    // first we get the content of the file
    let jsonContent = {};
    if (fs.existsSync(filePath)) {
      const fileContent = fs.readFileSync(filePath, { encoding: 'utf8' });
      jsonContent = JSON.parse(fileContent);
    }
 
    // check if the key is already present in our file
    // so we must not save the file again
    if (_.has(jsonContent, keyWithoutFile)) {
      return;
    }
 
    // and now we add the missing key
    jsonContent = _.set(jsonContent, keyWithoutFile, '');
 
    fs.writeFileSync(filePath, JSON.stringify(jsonContent, null, 2));
    this.logger.error(
      `The key "${keyWithoutFile}" for language: ${language} was added to the file "${filePart}.missing" (@ ${filePath} )`,
    );
  }
}