Newer
Older
CrypticOreWallet / src / pages / settings / translations / analyseLangs.ts
@Drew Lemmy Drew Lemmy on 9 Mar 2021 2 KB feat: more utils for translators
// Copyright (c) 2020-2021 Drew Lemmy
// This file is part of KristWeb 2 under GPL-3.0.
// Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt
import { Language, getLanguages } from "@utils/i18n";

export interface LangKeys { [key: string]: string }
export interface AnalysedLanguage {
  code: string;
  language: Language;

  error?: string;
  keys?: LangKeys;
  keyCount: number;
  missingKeys?: { k: string; v: string }[];
}

const IGNORE_KEYS = /_(?:plural|interval|male|female|\d+)$/;
export async function getLanguage([code, language]: [string, Language]): Promise<AnalysedLanguage> {
  const res = await fetch(`/locales/${code}.json`);
  if (!res.ok) throw new Error(res.statusText);

  const translation = await res.json();

  const isObject = (val: any) => typeof val === "object" && !Array.isArray(val);
  const addDelimiter = (a: string, b: string) => a ? `${a}.${b}` : b;

  // Find all translation keys recursively
  const keys = (obj: any = {}, head = ""): LangKeys =>
    Object.entries(obj)
      .reduce((product, [key, value]) => {
        // Ignore plurals, etc.
        if (IGNORE_KEYS.test(key)) return product;

        const fullPath = addDelimiter(head, key);
        return isObject(value as any)
          ? { ...product, ...keys(value, fullPath) }
          : {...product, [fullPath]: value };
      }, {});

  const langKeys = keys(translation);

  return { code, language, keys: langKeys, keyCount: Object.keys(langKeys).length };
}

export interface AnalysedLanguages {
  enKeyCount: number;
  languages: AnalysedLanguage[];
}

export async function analyseLanguages(): Promise<AnalysedLanguages | false> {
  const languages = getLanguages();
  if (!languages) return false;

  // Fetch the locale file for each language code
  const codes = Object.entries(languages);
  const languageData = await Promise.allSettled(codes.map(getLanguage));

  const en = languageData.find(l => l.status === "fulfilled" && l.value.code === "en");
  const enKeys = en?.status === "fulfilled" ? en?.value.keys || {} : {};
  const enKeyCount = enKeys ? Object.keys(enKeys).length : 1;

  return {
    enKeyCount,
    languages: languageData.map((result, i) => result.status === "fulfilled"
      ? {
        code: codes[i][0],
        language: codes[i][1],
        keys: result.value.keys,
        keyCount: result.value.keyCount,
        missingKeys: result.value.keys
          ? Object.entries(enKeys)
            .filter(([k]) => result.value.keys && !result.value.keys[k])
            .map(([k, v]) => ({ k, v }))
          : []
      }
      : { code: codes[i][0], language: codes[i][1], keyCount: 0, error: result.reason.toString() })
  };
}