dbmedialab/reader-critics

View on GitHub
src/app/services/localization/index.ts

Summary

Maintainability
A
35 mins
Test Coverage
//
// LESERKRITIKK v2 (aka Reader Critics)
// Copyright (C) 2017 DB Medialab/Aller Media AS, Oslo, Norway
// https://github.com/dbmedialab/reader-critics/
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software
// Foundation, either version 3 of the License, or (at your option) any later
// version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <http://www.gnu.org/licenses/>.
//

import { flatten } from 'flat';

import {
    isEmpty,
    isObject,
    isString,
    throttle,
} from 'lodash';

import Website from 'base/Website';
import config from 'app/config';
import * as app from 'app/util/applib';

const log = app.createLog();

const languageFile = 'resources/localization.json5';

export const systemLocale : string = config.get('localization.systemLocale');

interface Strings {
    app? : object;
    common? : object;
    frontend? : object;
}

let strings : Strings;

export function initLocalizationStrings() : Promise <void> {
    return installWatcher().then(loadLanguageFile);
}

function installWatcher() : Promise <void> {
    if (app.isProduction) {
        return Promise.resolve();
    }

    const throttledHandler = throttle(watchHandler, 1000, {
        leading: false,
        trailing: true,
    });

    return app.watchFile(languageFile, throttledHandler).then(watcher => {
        log(`Watching ${languageFile} for changes`);
    });
}
function loadLanguageFile() : Promise <void> {
    return app.loadJSON(languageFile).then(data => {
        strings = Object.freeze(data);
        log('Localization strings loaded');
    });
}

function watchHandler(eventType, filename) {
    if (eventType !== 'change') {
        return;
    }

    loadLanguageFile();
}

export function getFrontendStrings(website? : Website) : Promise <Object> {
    const allStrings = Object.assign({}, strings.common, strings.frontend);
    const locale = website ? website.locale : systemLocale;
    return Promise.resolve(flatten(applyLocale(allStrings, locale)));
}

export type TranslateOptions = {
    locale? : string
    values? : {}
};

export function translate(id : string, options? : string|TranslateOptions) : string {
    let usedLocale;

    // A single string value in "options" means to override the system default locale
    if (isString(options)) {
        usedLocale = options;
    }
    else if (isObject(options)) {
        usedLocale = options.locale || systemLocale;
    }

    const allStrings = Object.assign({}, strings.common, strings.app);
    const flattened = flatten(applyLocale(allStrings, usedLocale));

    if (!flattened[id]) {
        return id;
    }

    const replaceValues = (() => {
        if ((isEmpty(options) || isString(options)) ? true : isEmpty(options.values)) {
            return {};
        }

        return ((options as TranslateOptions).values || {});
    })();

    const replacer = (match : string) => {
        const index = match.substring(1, match.length - 1);
        return replaceValues[index] || '--';
    };

    return flattened[id].replace(/({\w+})/g, replacer);
}

function applyLocale(input : any, locale : string) : any {
    const a = {};

    Object.keys(input).forEach(key => {
        const o = input[key];

        if (isAllStrings(o)) {
            if (o[locale] !== undefined) {
                a[key] = o[locale];
            }
            else if (o[systemLocale] !== undefined) {
                a[key] = o[systemLocale];
            }
            else {
                a[key] = o['en'];
            }
        }
        else {
            a[key] = applyLocale(o, locale);
        }
    });

    return a;
}

const isAllStrings = (o : any) => {
    return 0 >= Object.values(o).filter(a => typeof a !== 'string').length;
};