ice framework documentation ice doc v1.10.1
Class Ice

I18n

    /**
 * Global alias of translate method.
 */
function _t(string! str, array values = null, var context = null, string lang = null)
{
    var i18n;

    let i18n = I18n::$fetch();

    if i18n {
        return i18n->translate(str, values, context, lang);
    }
}

namespace Ice;

use Ice\I18n\Plural;

/**
 * Internationalization (i18n) class.
 *
 * @package     Ice/I18n
 * @category    Library
 * @author      Ice Team
 * @copyright   (c) 2014-2023 Ice Team
 * @license     http://iceframework.org/license
 */
class I18n
{
    protected static i18n;
    protected cache = [];
    protected rules = [];
    protected options = [
        "source": "en-gb",
        "lang": "en-gb",
        "dir": ""
    ];

    /**
     * I18n constructor.
     *
     * @param array options
     */
    public function __construct(array options = [])
    {
        let this->options = array_merge(this->options, options),
            self::i18n = this;
    }

    /**
     * Return the last I18n created.
     *
     * @return I18n
     */
    public static function $fetch()
    {
        return self::i18n;
    }

    /**
     * Get and set the target language.
     *
     * @param string lang New language setting
     * @return string
     */
    public function lang(string lang = null) -> string
    {
        // Normalize the language
        if lang {
            let this->options["lang"] = strtolower(str_replace("_", "-", lang));
        }

        return this->options["lang"];
    }

    /**
     * Get ISO language code.
     *
     * @param string lang Language
     * @param boolean country Get country code, by default gets language code
     * @return string
     */
    public function iso(var lang = null, boolean country = false) -> string
    {
        var parts;

        if !lang {
            let lang = this->options["lang"];
        }

        let parts = explode("-", strtolower(str_replace("_", "-", lang)));

        return country && isset parts[1] ? parts[1] : parts[0];
    }



    /**
     * Returns specified form of a string translation. No parameters are replaced.
     * If no translation exists, the original string will be returned.
     *
     * @param string str Text to translate
     * @param mixed form If NULL, looking for `other` form, else the very first form
     * @param string lang Target language
     * @return string
     */
    public function get(string str, var form = null, string lang = null) -> string
    {
        var messages, translation;

        let messages = [];

        if !lang {
            // Use the global target language
            let lang = (string) this->options["lang"];
        }

        // Load the translation messages for this language
        let messages = this->load(lang);

        if isset messages[str] {
            let translation = messages[str];

            // If translation is array, return right context
            if typeof translation == "array" {
                // Get a value for the form key
                if array_key_exists(form, translation) {
                    return translation[form];
                } elseif array_key_exists("other", translation) {
                    return translation["other"];
                }

                // Return the very first form
                return reset(translation);
            }

            return translation;
        }

        // If no translation exists, return the original string
        return str;
    }

    /**
     * Load language from the file.
     *
     * @param string lang Language code
     * @return array
     */
    private function load(string lang) -> array
    {
        var cache, parts, subdir, tail, tmp, found, path, messages;

        // Load from the cache
        if fetch cache, this->cache[lang] {
            return cache;
        }

        let parts = explode("-", lang),
            subdir = implode(DIRECTORY_SEPARATOR, parts),
            messages = [];

        // Search for /en/gb.php, /en-gb.php, /en.php or gb.php
        for tail in  [subdir, lang, parts] {
            if typeof tail != "array" {
                let tmp = tail,
                    tail = [tmp];
            }

            for found in tail {
                let path = (string) this->options["dir"] . found . ".php";

                if file_exists(path) {
                    let messages = require path;
                    // Stop searching
                    break;
                }
            }
        }

        let this->cache[lang] = messages;

        return this->cache[lang];
    }

    /**
     * Returns translation of a string with right plural form.
     * If no translation exists, the original string will be returned.
     *
     * @param string string
     * @param int count
     * @param string lang
     * @return string
     */
    public function plural(string! str, int count = 0, string lang = null)
    {
        var rules, form, code;

        let code = this->iso(lang);

        if !fetch rules, this->rules[code] {
            // Get language code prefix
            let this->rules[code] = this->pluralRules(code),
                rules = this->rules[code];
        }


        let form = rules->getCategory(count);

        // Return the translation for that form
        return this->get(str, form, lang);
    }

    /**
     * Plural rules lazy initialization.
     *
     * @param string code Language code
     * @return object
     */
    protected function pluralRules(string code)
    {
        if code === "pl" {
            return new Plural\Polish;
        } elseif code === "ar" {
            return new Plural\Arabic;
        } elseif in_array(code, ["cs", "sk"], true) {
            return new Plural\Czech;
        } elseif in_array(code, ["ru", "sr", "uk", "sh", "be", "hr", "bs"], true) {
            return new Plural\Balkan;
        } elseif in_array(code, ["fr", "ff", "kab"], true) {
            return new Plural\French;
        } elseif in_array(code, ["mo", "ro"], true) {
            return new Plural\Romanian;
        } elseif in_array(code, ["hi", "ln", "mg", "ak", "tl", "am", "bh", "wa", "ti", "guw", "fil", "nso"], true) {
            return new Plural\Zero;
        } elseif in_array(code, [
            "en", "ny", "nr", "no", "om", "os", "ps", "pa", "nn", "or", "nl", "lg", "lb", "ky", "ml", "mr",
            "ne", "nd", "nb", "pt", "rm", "ts", "tn", "tk", "ur", "vo", "zu", "xh", "ve", "te", "ta", "sq",
            "so", "sn", "ss", "st", "sw", "sv", "ku", "mn", "et", "eo", "el", "eu", "fi", "fy", "fo", "ee",
            "dv", "bg", "af", "bn", "ca", "de", "da", "gl", "es", "it", "is", "ks", "ha", "kk", "kl", "gu",
            "brx", "mas", "teo", "chr", "cgg", "tig", "wae", "xog", "ast", "vun", "bem", "syr", "bez", "asa",
            "rof", "ksb", "rwk", "haw", "pap", "gsw", "fur", "saq", "seh", "nyn", "kcg", "ssy", "kaj", "jmc",
            "nah", "ckb"], true) {
            return new Plural\One;
        } elseif in_array(code, ["se", "kw", "iu", "smn", "sms", "smj", "sma", "naq", "smi"], true) {
            return new Plural\Two;
        } elseif in_array(code, [
            "my", "sg", "ms", "lo", "kn", "ko", "th", "to", "yo", "zh", "wo", "vi", "tr", "az", "km", "id",
            "ig", "fa", "dz", "bm", "bo", "ii", "hu", "ka", "jv", "ja", "kde", "ses", "sah", "kea"], true) {
            return new Plural\None;
        } else {
            throw new Exception("Unknown language code: " . code);
        }
    }

    /**
     * Alias of translate.
     */
    public function _(string! str, array values = null, var context = null, string lang = null) -> string
    {
        return this->translate(str, values, lang);
    }

    /**
     * Translation/internationalization function. strtr() or sprintf is used for replacing parameters.
     *
     * @param string string Text to translate
     * @param array values Values to replace in the translated text
     * @param mixed context String form or numeric count
     * @param string lang Source language
     * @return string
     */
    public function translate(string! str, array values = null, var context = null, string lang = null) -> string
    {
        if !lang {
            let lang = (string) this->options["lang"];
        }

        if is_numeric(context) {
            // Get plural form
            let str = this->plural(str, context, lang);
        } else {
            // Get custom form
            let str = this->get(str, context, lang);
        }

        if !count(values) {
            return str;
        } else {
            // Check if values is associative or sequential
            if count(array_filter(array_keys(values), "is_string")) {
                return strtr(str, values);
            } else {
                return call_user_func_array("sprintf", array_merge([str], values));
            }
        }
    }
}