2020-03-30 11:44:18 +00:00
|
|
|
import * as log from "tc-shared/log";
|
|
|
|
import {LogCategory} from "tc-shared/log";
|
|
|
|
import {guid} from "tc-shared/crypto/uid";
|
2020-07-19 16:49:00 +00:00
|
|
|
import {Settings, StaticSettings} from "tc-shared/settings";
|
2020-03-30 11:44:18 +00:00
|
|
|
import {createErrorModal} from "tc-shared/ui/elements/Modal";
|
|
|
|
import * as loader from "tc-loader";
|
2020-06-10 16:13:56 +00:00
|
|
|
import {formatMessage, formatMessageString} from "tc-shared/ui/frames/chat";
|
2020-03-30 11:44:18 +00:00
|
|
|
|
|
|
|
export interface TranslationKey {
|
|
|
|
message: string;
|
|
|
|
line?: number;
|
|
|
|
character?: number;
|
|
|
|
filename?: string;
|
2020-03-29 10:54:15 +00:00
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export interface Translation {
|
|
|
|
key: TranslationKey;
|
|
|
|
translated: string;
|
|
|
|
flags?: string[];
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export interface Contributor {
|
|
|
|
name: string;
|
|
|
|
email: string;
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export interface TranslationFile {
|
|
|
|
path: string;
|
|
|
|
full_url: string;
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
translations: Translation[];
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export interface RepositoryTranslation {
|
|
|
|
key: string;
|
|
|
|
path: string;
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
country_code: string;
|
|
|
|
name: string;
|
|
|
|
contributors: Contributor[];
|
|
|
|
}
|
2019-03-28 16:29:42 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
2020-03-30 11:44:18 +00:00
|
|
|
|
|
|
|
fast_translate[message] = translated;
|
|
|
|
return translated;
|
|
|
|
}
|
|
|
|
|
2020-06-10 16:13:56 +00:00
|
|
|
export function tra(message: string, ...args: (string | number | boolean)[]) : string;
|
|
|
|
export function tra(message: string, ...args: any[]) : JQuery[];
|
|
|
|
export function tra(message: string, ...args: any[]) : any {
|
2020-03-31 13:19:53 +00:00
|
|
|
message = /* @tr-ignore */ tr(message);
|
2020-06-10 16:13:56 +00:00
|
|
|
for(const element of args) {
|
|
|
|
if(typeof element !== "string" && typeof element !== "number" && typeof element !== "boolean")
|
|
|
|
return formatMessage(message, ...args);
|
|
|
|
}
|
|
|
|
if(message.indexOf("{:") !== -1)
|
|
|
|
return formatMessage(message, ...args);
|
|
|
|
return formatMessageString(message, ...args);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function traj(message: string, ...args: any[]) : JQuery[] {
|
|
|
|
return tra(message, ...args, {});
|
2020-03-30 11:44:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function load_translation_file(url: string, path: string) : Promise<TranslationFile> {
|
|
|
|
return new Promise<TranslationFile>((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.full_url = url;
|
|
|
|
file.path = path;
|
|
|
|
|
|
|
|
//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);
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
2020-03-30 11:44:18 +00:00
|
|
|
})
|
|
|
|
});
|
|
|
|
}
|
2018-12-05 19:46:33 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export function load_file(url: string, path: string) : Promise<void> {
|
|
|
|
return load_translation_file(url, path).then(async result => {
|
|
|
|
/* TODO: Improve this test?!*/
|
|
|
|
try {
|
|
|
|
tr("Dummy translation test");
|
|
|
|
} catch(error) {
|
|
|
|
throw "dummy test failed";
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
log.info(LogCategory.I18N, tr("Successfully initialized up translation file from %s"), url);
|
|
|
|
translations = result.translations;
|
|
|
|
}).catch(error => {
|
|
|
|
log.warn(LogCategory.I18N, tr("Failed to load translation file from \"%s\". Error: %o"), url, error);
|
|
|
|
return Promise.reject(error);
|
|
|
|
});
|
|
|
|
}
|
2019-05-24 20:14:02 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
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) => {
|
2018-12-15 13:04:29 +00:00
|
|
|
$.ajax({
|
2020-03-30 11:44:18 +00:00
|
|
|
url: repo.url + "/info.json",
|
2018-12-15 13:04:29 +00:00
|
|
|
async: true,
|
2020-03-30 11:44:18 +00:00
|
|
|
cache: !reload,
|
2018-12-15 13:04:29 +00:00
|
|
|
success: result => {
|
2020-03-30 11:44:18 +00:00
|
|
|
const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile;
|
|
|
|
if(!file) {
|
|
|
|
reject("Invalid json");
|
|
|
|
return;
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
2020-03-30 11:44:18 +00:00
|
|
|
|
|
|
|
resolve(file);
|
2018-12-15 13:04:29 +00:00
|
|
|
},
|
|
|
|
error: (xhr, error) => {
|
|
|
|
reject(tr("Failed to load file: ") + error);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
Object.assign(repo, info_json);
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
if(!repo.unique_id)
|
|
|
|
repo.unique_id = guid();
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
repo.translations = repo.translations || [];
|
|
|
|
repo.load_timestamp = Date.now();
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export async function load_repository(url: string) : Promise<TranslationRepository> {
|
|
|
|
const result = {} as TranslationRepository;
|
|
|
|
result.url = url;
|
|
|
|
await load_repository0(result, false);
|
|
|
|
return result;
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export namespace config {
|
|
|
|
export interface TranslationConfig {
|
|
|
|
current_repository_url?: string;
|
|
|
|
current_language?: string;
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
current_translation_url?: string;
|
|
|
|
current_translation_path?: string;
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export interface RepositoryConfig {
|
|
|
|
repositories?: {
|
|
|
|
url?: string;
|
|
|
|
repository?: TranslationRepository;
|
|
|
|
}[];
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
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);
|
|
|
|
let config: RepositoryConfig;
|
|
|
|
try {
|
|
|
|
config = config_string ? JSON.parse(config_string) : {};
|
|
|
|
} catch(error) {
|
|
|
|
log.error(LogCategory.I18N, tr("Failed to parse repository config: %o"), error);
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
2020-03-30 11:44:18 +00:00
|
|
|
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
|
2020-07-19 16:49:00 +00:00
|
|
|
load_repository(StaticSettings.instance.static(Settings.KEY_I18N_DEFAULT_REPOSITORY)).then(repo => {
|
2020-03-30 11:44:18 +00:00
|
|
|
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);
|
|
|
|
});
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
return _cached_repository_config = config;
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export function save_repository_config() {
|
|
|
|
localStorage.setItem(repository_config_key, JSON.stringify(_cached_repository_config));
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
const translation_config_key = "i18n.translation";
|
|
|
|
let _cached_translation_config: TranslationConfig;
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export function translation_config() : TranslationConfig {
|
|
|
|
if(_cached_translation_config)
|
2018-12-15 13:04:29 +00:00
|
|
|
return _cached_translation_config;
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
const config_string = localStorage.getItem(translation_config_key);
|
|
|
|
try {
|
|
|
|
_cached_translation_config = config_string ? JSON.parse(config_string) : {};
|
|
|
|
} catch(error) {
|
|
|
|
log.error(LogCategory.I18N, tr("Failed to initialize translation config. Using default one. Error: %o"), error);
|
|
|
|
_cached_translation_config = {} as any;
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
2020-03-30 11:44:18 +00:00
|
|
|
return _cached_translation_config;
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export function save_translation_config() {
|
|
|
|
localStorage.setItem(translation_config_key, JSON.stringify(_cached_translation_config));
|
|
|
|
}
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export function register_repository(repository: TranslationRepository) {
|
|
|
|
if(!repository) return;
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
for(const repo of config.repository_config().repositories)
|
|
|
|
if(repo.url == repository.url) return;
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
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);
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export function delete_repository(repository: TranslationRepository) {
|
|
|
|
if(!repository) return;
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
for(const repo of [...config.repository_config().repositories])
|
|
|
|
if(repo.url == repository.url) {
|
|
|
|
config.repository_config().repositories.remove(repo);
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
2020-03-30 11:44:18 +00:00
|
|
|
config.save_repository_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function iterate_repositories(callback_entry: (repository: TranslationRepository) => any) {
|
|
|
|
const promises = [];
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
for(const repository of registered_repositories()) {
|
|
|
|
promises.push(load_repository0(repository, false).then(() => callback_entry(repository)).catch(error => {
|
|
|
|
log.warn(LogCategory.I18N, "Failed to fetch repository %s. error: %o", repository.url, error);
|
|
|
|
}));
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
await Promise.all(promises);
|
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
export function select_translation(repository: TranslationRepository, entry: RepositoryTranslation) {
|
|
|
|
const cfg = config.translation_config();
|
|
|
|
|
|
|
|
if(entry && repository) {
|
|
|
|
cfg.current_language = entry.name;
|
|
|
|
cfg.current_repository_url = repository.url;
|
|
|
|
cfg.current_translation_url = repository.url + entry.path;
|
|
|
|
cfg.current_translation_path = entry.path;
|
|
|
|
} else {
|
|
|
|
cfg.current_language = undefined;
|
|
|
|
cfg.current_repository_url = undefined;
|
|
|
|
cfg.current_translation_url = undefined;
|
|
|
|
cfg.current_translation_path = undefined;
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
config.save_translation_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ATTENTION: This method is called before most other library initialisations! */
|
|
|
|
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, cfg.current_translation_path);
|
|
|
|
} catch (error) {
|
|
|
|
console.error(tr("Failed to initialize selected translation: %o"), error);
|
|
|
|
const show_error = () => {
|
|
|
|
createErrorModal(tr("Translation System"), tra("Failed to load current selected translation file.{:br:}File: {0}{:br:}Error: {1}{:br:}{:br:}Using default fallback translations.", cfg.current_translation_url, error)).open()
|
|
|
|
};
|
|
|
|
if(loader.running())
|
|
|
|
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
|
|
|
|
priority: 10,
|
|
|
|
function: async () => show_error(),
|
|
|
|
name: "I18N error display"
|
|
|
|
});
|
|
|
|
else
|
|
|
|
show_error();
|
2018-12-15 13:04:29 +00:00
|
|
|
}
|
2018-12-05 19:46:33 +00:00
|
|
|
}
|
2020-03-30 11:44:18 +00:00
|
|
|
// 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");
|
2018-12-05 19:46:33 +00:00
|
|
|
}
|
2018-12-15 13:04:29 +00:00
|
|
|
|
2020-03-30 11:44:18 +00:00
|
|
|
window.tr = tr;
|
|
|
|
window.tra = tra;
|