/* "key": { "message": "Show permission description", "line": 374, "character": 30, "filename": "/home/wolverindev/TeaSpeak/TeaSpeak/Web-Client/shared/js/ui/modal/ModalPermissionEdit.ts" }, "translated": "Berechtigungsbeschreibung anzeigen", "flags": [ "google-translate", "verified" ] */ function guid() { function s4() { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) .substring(1); } return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); } namespace i18n { export interface TranslationKey { message: string; line?: number; character?: number; filename?: string; } export interface Translation { key: TranslationKey; translated: string; flags?: string[]; } export interface Contributor { name: string; email: string; } export interface FileInfo { name: string; contributors: Contributor[]; } export interface TranslationFile { url: string; info: FileInfo; translations: Translation[]; } export interface RepositoryTranslation { key: string; path: string; } export interface TranslationRepository { unique_id: string; url: string; name?: string; contact?: string; translations?: RepositoryTranslation[]; load_timestamp?: number; } let translations: Translation[] = []; let fast_translate: { [key:string]:string; } = {}; export function tr(message: string, key?: string) { const sloppy = fast_translate[message]; if(sloppy) return sloppy; log.info(LogCategory.I18N, "Translating \"%s\". Default: \"%s\"", key, message); let translated = message; for(const translation of translations) { if(translation.key.message == message) { translated = translation.translated; break; } } fast_translate[message] = translated; return translated; } async function load_translation_file(url: string) : Promise { return new Promise((resolve, reject) => { $.ajax({ url: url, async: true, success: result => { try { const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile; if(!file) { reject("Invalid json"); return; } file.url = url; //TODO validate file resolve(file); } catch(error) { log.warn(LogCategory.I18N, tr("Failed to load translation file %s. Failed to parse or process json: %o"), url, error); reject(tr("Failed to process or parse json!")); } }, error: (xhr, error) => { reject(tr("Failed to load file: ") + error); } }) }); } export function load_file(url: string) : Promise { return load_translation_file(url).then(result => { log.info(LogCategory.I18N, tr("Successfully initialized up translation file from %s"), url); translations = result.translations; return Promise.resolve(); }).catch(error => { log.warn(LogCategory.I18N, tr("Failed to load translation file from \"%s\". Error: %o"), url, error); return Promise.reject(error); }); } async function load_repository0(repo: TranslationRepository, reload: boolean) { if(!repo.load_timestamp || repo.load_timestamp < 1000 || reload) { const info_json = await new Promise((resolve, reject) => { $.ajax({ url: repo.url + "/info.json", async: true, cache: !reload, success: result => { const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile; if(!file) { reject("Invalid json"); return; } resolve(file); }, error: (xhr, error) => { reject(tr("Failed to load file: ") + error); } }) }); Object.assign(repo, info_json); } if(!repo.unique_id) repo.unique_id = guid(); repo.translations = repo.translations || []; repo.load_timestamp = Date.now(); } export async function load_repository(url: string) : Promise { const result = {} as TranslationRepository; result.url = url; await load_repository0(result, false); return result; } export namespace config { export interface TranslationConfig { current_repository_url?: string; current_language?: string; current_translation_url: string; } export interface RepositoryConfig { repositories?: { url?: string; repository?: TranslationRepository; }[]; } const repository_config_key = "i18n.repository"; let _cached_repository_config: RepositoryConfig; export function repository_config() { if(_cached_repository_config) return _cached_repository_config; const config_string = localStorage.getItem(repository_config_key); const config: RepositoryConfig = config_string ? JSON.parse(config_string) : {}; config.repositories = config.repositories || []; for(const repo of config.repositories) (repo.repository || {load_timestamp: 0}).load_timestamp = 0; if(config.repositories.length == 0) { //Add the default TeaSpeak repository load_repository(StaticSettings.instance.static("i18n.default_repository", "i18n/")).then(repo => { log.info(LogCategory.I18N, tr("Successfully added default repository from \"%s\"."), repo.url); register_repository(repo); }).catch(error => { log.warn(LogCategory.I18N, tr("Failed to add default repository. Error: %o"), error); }); } return _cached_repository_config = config; } export function save_repository_config() { localStorage.setItem(repository_config_key, JSON.stringify(_cached_repository_config)); } const translation_config_key = "i18n.translation"; let _cached_translation_config: TranslationConfig; export function translation_config() : TranslationConfig { if(_cached_translation_config) return _cached_translation_config; const config_string = localStorage.getItem(translation_config_key); _cached_translation_config = config_string ? JSON.parse(config_string) : {}; return _cached_translation_config; } export function save_translation_config() { localStorage.setItem(translation_config_key, JSON.stringify(_cached_translation_config)); } } export function register_repository(repository: TranslationRepository) { if(!repository) return; for(const repo of config.repository_config().repositories) if(repo.url == repository.url) return; config.repository_config().repositories.push(repository); config.save_repository_config(); } export function registered_repositories() : TranslationRepository[] { return config.repository_config().repositories.map(e => e.repository || {url: e.url, load_timestamp: 0} as TranslationRepository); } export function delete_repository(repository: TranslationRepository) { if(!repository) return; for(const repo of [...config.repository_config().repositories]) if(repo.url == repository.url) { config.repository_config().repositories.remove(repo); } config.save_repository_config(); } export function iterate_translations(callback_entry: (repository: TranslationRepository, entry: TranslationFile) => any, callback_finish: () => any) { let count = 0; const update_finish = () => { if(count == 0 && callback_finish) callback_finish(); }; for(const repo of registered_repositories()) { count++; load_repository0(repo, false).then(() => { for(const translation of repo.translations || []) { const translation_path = repo.url + "/" + translation.path; count++; load_translation_file(translation_path).then(file => { if(callback_entry) { try { callback_entry(repo, file); } catch (error) { console.error(error); //TODO more error handling? } } count--; update_finish(); }).catch(error => { log.warn(LogCategory.I18N, tr("Failed to load translation file for repository %s. Translation: %s (%s) Error: %o"), repo.name, translation.key, translation_path, error); count--; update_finish(); }); } count--; update_finish(); }).catch(error => { log.warn(LogCategory.I18N, tr("Failed to load repository while iteration: %s (%s). Error: %o"), (repo || {name: "unknown"}).name, (repo || {url: "unknown"}).url, error); count--; update_finish(); }); } update_finish(); } export function select_translation(repository: TranslationRepository, entry: TranslationFile) { const cfg = config.translation_config(); if(entry && repository) { cfg.current_language = entry.info.name; cfg.current_repository_url = repository.url; cfg.current_translation_url = entry.url; } else { cfg.current_language = undefined; cfg.current_repository_url = undefined; cfg.current_translation_url = undefined; } config.save_translation_config(); } export async function initialize() { const rcfg = config.repository_config(); /* initialize */ const cfg = config.translation_config(); if(cfg.current_translation_url) { try { await load_file(cfg.current_translation_url); } catch (error) { createErrorModal(tr("Translation System"), tr("Failed to load current selected translation file.") + "
File: " + cfg.current_translation_url + "
Error: " + error + "
" + tr("Using default fallback translations.")).open(); } } // await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/de_DE.translation"); // await load_file("http://localhost/home/TeaSpeak/TeaSpeak/Web-Client/web/environment/development/i18n/test.json"); } } const tr: typeof i18n.tr = i18n.tr;