Implementation of the translation system, inc generators.
parent
2293302fbd
commit
27a92af825
|
@ -9,7 +9,7 @@ generated/
|
|||
node_modules/
|
||||
auth/certs/
|
||||
auth/js/auth.js.map
|
||||
|
||||
package-lock.json
|
||||
|
||||
.sass-cache/
|
||||
.idea/
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as ts from "typescript";
|
||||
import * as generator from "./generator";
|
||||
import * as generator from "./ts_generator";
|
||||
|
||||
import {readFileSync} from "fs";
|
||||
import * as glob from "glob";
|
||||
|
@ -48,7 +48,6 @@ function compile(fileNames: string[], options: ts.CompilerOptions): void {
|
|||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
console.log("Arguments: " + process.argv.slice(2));
|
||||
const config = ts.parseCommandLine(process.argv.slice(2), file => readFileSync(file).toString());
|
||||
console.dir(config);
|
||||
if(config.errors && config.errors.length > 0) {
|
||||
|
|
|
@ -1,280 +1,3 @@
|
|||
import * as ts from "typescript";
|
||||
import * as sha256 from "sha256";
|
||||
import {SyntaxKind} from "typescript";
|
||||
|
||||
|
||||
export function generate(file: ts.SourceFile, config: Configuration) : TranslationEntry[] {
|
||||
let result: TranslationEntry[] = [];
|
||||
|
||||
file.forEachChild(n => _generate(config, n, result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function report(node: ts.Node, message: string) {
|
||||
const sf = node.getSourceFile();
|
||||
let { line, character } = sf ? sf.getLineAndCharacterOfPosition(node.getStart()) : {line: -1, character: -1};
|
||||
console.log(`${(sf || {fileName: "unknown"}).fileName} (${line + 1},${character + 1}): ${message}`);
|
||||
}
|
||||
|
||||
function _generate(config: Configuration, node: ts.Node, result: TranslationEntry[]) {
|
||||
//console.log("Node: %s", SyntaxKind[node.kind]);
|
||||
|
||||
call_analize:
|
||||
if(ts.isCallExpression(node)) {
|
||||
const call = <ts.CallExpression>node;
|
||||
const call_name = call.expression["escapedText"] as string;
|
||||
if(call_name != "tr") break call_analize;
|
||||
|
||||
console.dir(call_name);
|
||||
|
||||
console.log("Parameters: %o", call.arguments.length);
|
||||
if(call.arguments.length > 1) {
|
||||
report(call, "Invalid argument count");
|
||||
node.forEachChild(n => _generate(config, n, result));
|
||||
return;
|
||||
}
|
||||
|
||||
const object = <ts.StringLiteral>call.arguments[0];
|
||||
if(object.kind != SyntaxKind.StringLiteral) {
|
||||
report(call, "Invalid argument: " + SyntaxKind[object.kind]);
|
||||
node.forEachChild(n => _generate(config, n, result));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Message: %o", object.text);
|
||||
|
||||
//FIXME
|
||||
if(config.replace_cache) {
|
||||
console.log("Update!");
|
||||
ts.updateCall(call, call.expression, call.typeArguments, [ts.createLiteral("PENIS!")]);
|
||||
}
|
||||
|
||||
const { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
|
||||
result.push({
|
||||
filename: node.getSourceFile().fileName,
|
||||
line: line,
|
||||
character: character,
|
||||
message: object.text
|
||||
});
|
||||
}
|
||||
|
||||
node.forEachChild(n => _generate(config, n, result));
|
||||
}
|
||||
function create_unique_check(source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] {
|
||||
const nodes: ts.Node[] = [], blocked_nodes: ts.Statement[] = [];
|
||||
|
||||
const node_path = (node: ts.Node) => {
|
||||
const sf = node.getSourceFile();
|
||||
let { line, character } = sf ? sf.getLineAndCharacterOfPosition(node.getStart()) : {line: -1, character: -1};
|
||||
return `${(sf || {fileName: "unknown"}).fileName} (${line + 1},${character + 1})`;
|
||||
};
|
||||
|
||||
const create_error = (variable_name: ts.Expression, variable_path: ts.Expression, other_path: ts.Expression) => {
|
||||
return [
|
||||
ts.createLiteral("Translation with generated name \""),
|
||||
variable_name,
|
||||
ts.createLiteral("\" already exists!\nIt has been already defined here: "),
|
||||
other_path,
|
||||
ts.createLiteral("\nAttempted to redefine here: "),
|
||||
variable_path,
|
||||
ts.createLiteral("\nRegenerate and/or fix your program!")
|
||||
].reduce((a, b) => ts.createBinary(a, SyntaxKind.PlusToken, b));
|
||||
};
|
||||
|
||||
let declarations_file: ts.Expression;
|
||||
const unique_check_label_name = "unique_translation_check";
|
||||
|
||||
/* initialization */
|
||||
{
|
||||
const declarations = ts.createElementAccess(variable, ts.createLiteral("declared"));
|
||||
nodes.push(ts.createAssignment(declarations, ts.createBinary(declarations, SyntaxKind.BarBarToken, ts.createAssignment(declarations, ts.createObjectLiteral()))));
|
||||
|
||||
declarations_file = ts.createElementAccess(variable, ts.createLiteral("declared_files"));
|
||||
nodes.push(ts.createAssignment(declarations_file, ts.createBinary(declarations_file, SyntaxKind.BarBarToken, ts.createAssignment(declarations_file, ts.createObjectLiteral()))));
|
||||
|
||||
variable = declarations;
|
||||
}
|
||||
|
||||
/* test file already loaded */
|
||||
{
|
||||
const unique_id = sha256(source_file.fileName + " | " + (Date.now() / 1000));
|
||||
const property = ts.createElementAccess(declarations_file, ts.createLiteral(unique_id));
|
||||
|
||||
const if_condition = ts.createBinary(property, SyntaxKind.ExclamationEqualsEqualsToken, ts.createIdentifier("undefined"));
|
||||
//
|
||||
let if_then: ts.Block;
|
||||
{
|
||||
const elements: ts.Statement[] = [];
|
||||
|
||||
const console = ts.createIdentifier("console.warn");
|
||||
elements.push(ts.createCall(console, [], [ts.createLiteral("This file has already been loaded!\nAre you executing scripts twice?") as any]));
|
||||
elements.push(ts.createBreak(unique_check_label_name));
|
||||
|
||||
if_then = ts.createBlock(elements);
|
||||
}
|
||||
|
||||
const if_else = ts.createAssignment(property, ts.createLiteral(unique_id));
|
||||
blocked_nodes.push(ts.createIf(if_condition, if_then, if_else as any));
|
||||
}
|
||||
|
||||
/* test if variable has been defined somewhere else */
|
||||
{
|
||||
const for_variable_name = ts.createLoopVariable();
|
||||
const for_variable_path = ts.createLoopVariable();
|
||||
const for_declaration = ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createObjectBindingPattern([
|
||||
ts.createBindingElement(undefined, "name", for_variable_name, undefined),
|
||||
ts.createBindingElement(undefined, "path", for_variable_path, undefined)])
|
||||
, undefined, undefined)]);
|
||||
|
||||
let for_block: ts.Statement;
|
||||
{ //Create the for block
|
||||
const elements: ts.Statement[] = [];
|
||||
|
||||
|
||||
const property = ts.createElementAccess(variable, for_variable_name);
|
||||
const if_condition = ts.createBinary(property, SyntaxKind.ExclamationEqualsEqualsToken, ts.createIdentifier("undefined"));
|
||||
|
||||
//
|
||||
const if_then = ts.createThrow(create_error(for_variable_name, for_variable_path, property));
|
||||
const if_else = ts.createAssignment(property, for_variable_path);
|
||||
const if_valid = ts.createIf(if_condition, if_then, if_else as any);
|
||||
|
||||
elements.push(if_valid);
|
||||
|
||||
for_block = ts.createBlock(elements);
|
||||
}
|
||||
|
||||
let block = ts.createForOf(undefined,
|
||||
for_declaration, ts.createArrayLiteral(
|
||||
[...variables.map(e => ts.createObjectLiteral([
|
||||
ts.createPropertyAssignment("name", ts.createLiteral(e.name)),
|
||||
ts.createPropertyAssignment("path", ts.createLiteral(node_path(e.node)))
|
||||
]))
|
||||
])
|
||||
, for_block);
|
||||
block = ts.addSyntheticLeadingComment(block, SyntaxKind.MultiLineCommentTrivia, "Auto generated helper for testing if the translation keys are unique", true);
|
||||
blocked_nodes.push(block);
|
||||
}
|
||||
return [...nodes, ts.createLabel(unique_check_label_name, ts.createBlock(blocked_nodes))];
|
||||
}
|
||||
|
||||
export function transform(config: Configuration, context: ts.TransformationContext, node: ts.SourceFile) : TransformResult {
|
||||
const cache: VolatileTransformConfig = {} as any;
|
||||
cache.translations = [];
|
||||
|
||||
//Initialize nodes
|
||||
const extra_nodes: ts.Node[] = [];
|
||||
{
|
||||
cache.nodes = {} as any;
|
||||
if(config.use_window) {
|
||||
const window = ts.createIdentifier("window");
|
||||
let translation_map = ts.createPropertyAccess(window, ts.createIdentifier("_translations"));
|
||||
const new_translations = ts.createAssignment(translation_map, ts.createObjectLiteral());
|
||||
|
||||
let translation_map_init: ts.Expression = ts.createBinary(translation_map, ts.SyntaxKind.BarBarToken, new_translations);
|
||||
translation_map_init = ts.createParen(translation_map_init);
|
||||
|
||||
cache.nodes = {
|
||||
translation_map: translation_map,
|
||||
translation_map_init: translation_map_init
|
||||
};
|
||||
} else {
|
||||
const variable_name = "_translations";
|
||||
const variable_map = ts.createIdentifier(variable_name);
|
||||
|
||||
const inline_if = ts.createBinary(ts.createBinary(ts.createTypeOf(variable_map), SyntaxKind.ExclamationEqualsEqualsToken, ts.createLiteral("undefined")), ts.SyntaxKind.BarBarToken, ts.createAssignment(variable_map, ts.createObjectLiteral()));
|
||||
|
||||
cache.nodes = {
|
||||
translation_map: variable_map,
|
||||
translation_map_init: variable_map
|
||||
};
|
||||
|
||||
//ts.createVariableDeclarationList([ts.createVariableDeclaration(variable_name)], ts.NodeFlags.Let)
|
||||
extra_nodes.push(inline_if);
|
||||
}
|
||||
}
|
||||
|
||||
const generated_names: { name: string, node: ts.Node }[] = [];
|
||||
cache.name_generator = (config, node, message) => {
|
||||
const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let name = "";
|
||||
while(name.length < 8) {
|
||||
const char = characters[Math.floor(Math.random() * characters.length)];
|
||||
name = name + char;
|
||||
if(name[0] >= '0' && name[0] <= '9')
|
||||
name = name.substr(1) || "";
|
||||
}
|
||||
|
||||
//FIXME
|
||||
//if(generated_names.indexOf(name) != -1)
|
||||
// return cache.name_generator(config, node, message);
|
||||
|
||||
generated_names.push({name: name, node: node});
|
||||
return name;
|
||||
};
|
||||
|
||||
function visit(node: ts.Node): ts.Node {
|
||||
node = ts.visitEachChild(node, visit, context);
|
||||
return replace_processor(config, cache, node);
|
||||
}
|
||||
node = ts.visitNode(node, visit);
|
||||
extra_nodes.push(...create_unique_check(node, cache.nodes.translation_map_init, generated_names));
|
||||
|
||||
node = ts.updateSourceFileNode(node, [...(extra_nodes as any[]), ...node.statements], node.isDeclarationFile, node.referencedFiles, node.typeReferenceDirectives, node.hasNoDefaultLib, node.referencedFiles);
|
||||
|
||||
const result: TransformResult = {} as any;
|
||||
result.node = node;
|
||||
result.translations = cache.translations;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function replace_processor(config: Configuration, cache: VolatileTransformConfig, node: ts.Node) : ts.Node {
|
||||
if(config.verbose)
|
||||
console.log("Process %s", SyntaxKind[node.kind]);
|
||||
if(ts.isCallExpression(node)) {
|
||||
const call = <ts.CallExpression>node;
|
||||
const call_name = call.expression["escapedText"] as string;
|
||||
if(call_name != "tr") return node;
|
||||
|
||||
if(config.verbose) {
|
||||
console.dir(call_name);
|
||||
console.log("Parameters: %o", call.arguments.length);
|
||||
}
|
||||
if(call.arguments.length > 1) {
|
||||
report(call, "Invalid argument count");
|
||||
return node;
|
||||
}
|
||||
|
||||
const object = <ts.StringLiteral>call.arguments[0];
|
||||
if(object.kind != SyntaxKind.StringLiteral) {
|
||||
report(call, "Invalid argument: " + SyntaxKind[object.kind]);
|
||||
return node;
|
||||
}
|
||||
|
||||
if(config.verbose)
|
||||
console.log("Message: %o", object.text || object.getText());
|
||||
|
||||
const variable_name = ts.createIdentifier(cache.name_generator(config, node, object.text || object.getText()));
|
||||
const variable_init = ts.createPropertyAccess(cache.nodes.translation_map_init, variable_name);
|
||||
|
||||
const variable = ts.createPropertyAccess(cache.nodes.translation_map, variable_name);
|
||||
const new_variable = ts.createAssignment(variable, call);
|
||||
|
||||
const source_file = node.getSourceFile();
|
||||
let { line, character } = source_file ? source_file.getLineAndCharacterOfPosition(node.getStart()) : {line: -1, character: -1};
|
||||
|
||||
cache.translations.push({
|
||||
message: object.text || object.getText(),
|
||||
line: line,
|
||||
character: character,
|
||||
filename: (source_file || {fileName: "unknown"}).fileName
|
||||
});
|
||||
|
||||
return ts.createBinary(variable_init, ts.SyntaxKind.BarBarToken, new_variable);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
export interface TranslationEntry {
|
||||
filename: string;
|
||||
|
@ -282,25 +5,4 @@ export interface TranslationEntry {
|
|||
character: number;
|
||||
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Configuration {
|
||||
use_window?: boolean;
|
||||
replace_cache?: boolean;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export interface TransformResult {
|
||||
node: ts.SourceFile;
|
||||
translations: TranslationEntry[];
|
||||
}
|
||||
|
||||
interface VolatileTransformConfig {
|
||||
nodes: {
|
||||
translation_map: ts.Expression;
|
||||
translation_map_init: ts.Expression;
|
||||
};
|
||||
|
||||
name_generator: (config: Configuration, node: ts.Node, message: string) => string;
|
||||
translations: TranslationEntry[];
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
import * as ts from "typescript";
|
||||
import * as generator from "./generator";
|
||||
import * as ts_generator from "./ts_generator";
|
||||
import * as jsrender_generator from "./jsrender_generator";
|
||||
import {readFileSync, writeFileSync} from "fs";
|
||||
import * as path from "path";
|
||||
import * as glob from "glob";
|
||||
import {isArray} from "util";
|
||||
import * as mkdirp from "mkdirp";
|
||||
import {TranslationEntry} from "./generator";
|
||||
|
||||
console.log("TR GEN!");
|
||||
|
||||
|
@ -65,7 +68,7 @@ if(config.verbose) {
|
|||
console.log("Input files:");
|
||||
for(const file of config.source_files)
|
||||
console.log(" - " + file);
|
||||
console.log("Target file: " + config.target_file;
|
||||
console.log("Target file: " + config.target_file);
|
||||
}
|
||||
|
||||
const negate_files: string[] = [].concat.apply([], (config.excluded_files || []).map(file => glob.sync(config.base_bath + file))).map(file => path.normalize(file));
|
||||
|
@ -94,9 +97,10 @@ function print(nodes: ts.Node[] | ts.SourceFile) : string {
|
|||
);
|
||||
}
|
||||
|
||||
const translations: TranslationEntry[] = [];
|
||||
config.source_files.forEach(file => {
|
||||
if(config.verbose)
|
||||
console.log("iterating over %s", file)
|
||||
console.log("iterating over %s (%s)", file, path.resolve(path.normalize(config.base_bath + file)));
|
||||
glob.sync(config.base_bath + file).forEach(_file => {
|
||||
_file = path.normalize(_file);
|
||||
for(const n_file of negate_files) {
|
||||
|
@ -106,37 +110,52 @@ config.source_files.forEach(file => {
|
|||
}
|
||||
}
|
||||
|
||||
let source = ts.createSourceFile(
|
||||
_file,
|
||||
readFileSync(_file).toString(),
|
||||
ts.ScriptTarget.ES2016,
|
||||
true
|
||||
);
|
||||
console.log(print(source));
|
||||
const file_type = path.extname(_file);
|
||||
if(file_type == ".ts") {
|
||||
let source = ts.createSourceFile(
|
||||
_file,
|
||||
readFileSync(_file).toString(),
|
||||
ts.ScriptTarget.ES2016,
|
||||
true
|
||||
);
|
||||
console.log(print(source));
|
||||
|
||||
console.log("Compile " + _file);
|
||||
console.log("Compile " + _file);
|
||||
|
||||
const messages = generator.generate(source, {
|
||||
replace_cache: true
|
||||
});
|
||||
messages.forEach(message => {
|
||||
console.log(message);
|
||||
});
|
||||
const messages = ts_generator.generate(source, {});
|
||||
translations.push(...messages);
|
||||
|
||||
console.log("PRINT!");
|
||||
console.log(print(source));
|
||||
/*
|
||||
result += "\n\n" + decl.print(decl.generate(source, {
|
||||
remove_private: false
|
||||
}));
|
||||
*/
|
||||
/*
|
||||
messages.forEach(message => {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
console.log("PRINT!");
|
||||
console.log(print(source));
|
||||
*/
|
||||
} else if(file_type == ".html") {
|
||||
const messages = jsrender_generator.generate({}, {
|
||||
content: readFileSync(_file).toString(),
|
||||
name: _file
|
||||
});
|
||||
translations.push(...messages);
|
||||
/*
|
||||
messages.forEach(message => {
|
||||
console.log(message);
|
||||
});
|
||||
*/
|
||||
} else {
|
||||
console.log("Unknown file type \"%s\". Skipping file %s", file_type, _file);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
mkdirp(path.normalize(path.dirname(base_path + "/" + target_file)), error => {
|
||||
if(error)
|
||||
throw error;
|
||||
writeFileSync(base_path + "/" + target_file, result);
|
||||
});
|
||||
*/
|
||||
if(config.target_file) {
|
||||
mkdirp(path.normalize(path.dirname(config.base_bath + config.target_file)), error => {
|
||||
if(error)
|
||||
throw error;
|
||||
writeFileSync(config.base_bath + config.target_file, JSON.stringify(translations, null, 2));
|
||||
});
|
||||
} else {
|
||||
console.log(JSON.stringify(translations, null, 2));
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import {TranslationEntry} from "./generator";
|
||||
|
||||
export interface Configuration {
|
||||
|
||||
}
|
||||
export interface File {
|
||||
content: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/* Well my IDE hates me and does not support groups. By default with ES6 groups are supported... nvm */
|
||||
//const regex = /{{ *tr *(?<message_expression>(("([^"]|\\")*")|('([^']|\\')*')|(`([^`]|\\`)+`)|( *\+ *)?)+) *\/ *}}/;
|
||||
const regex = /{{ *tr *((("([^"]|\\")*")|('([^']|\\')*')|(`([^`]|\\`)+`)|([\n ]*\+[\n ]*)?)+) *\/ *}}/;
|
||||
export function generate(config: Configuration, file: File) : TranslationEntry[] {
|
||||
let result: TranslationEntry[] = [];
|
||||
|
||||
const lines = file.content.split('\n');
|
||||
let match: RegExpExecArray;
|
||||
let base_index = 0;
|
||||
|
||||
while(match = regex.exec(file.content.substr(base_index))) {
|
||||
let expression = ((<any>match).groups || {})["message_expression"] || match[1];
|
||||
//expression = expression.replace(/\n/g, "\\n");
|
||||
|
||||
let message;
|
||||
try {
|
||||
message = eval(expression);
|
||||
} catch (error) {
|
||||
console.error("Failed to evaluate expression:\n%s", expression);
|
||||
base_index += match.index + match[0].length;
|
||||
continue;
|
||||
}
|
||||
|
||||
let character = base_index + match.index;
|
||||
let line;
|
||||
|
||||
for(line = 0; line < lines.length; line++) {
|
||||
const length = lines[line].length + 1;
|
||||
if(length > character) break;
|
||||
character -= length;
|
||||
}
|
||||
|
||||
result.push({
|
||||
filename: file.name,
|
||||
character: character + 1,
|
||||
line: line + 1,
|
||||
message: message
|
||||
});
|
||||
|
||||
base_index += match.index + match[0].length;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -19,7 +19,7 @@ debugger;
|
|||
debugger;
|
||||
const zzz = true ? "yyy" : "bbb";
|
||||
|
||||
const y
|
||||
const y = "";
|
||||
debugger;
|
||||
debugger;
|
||||
debugger;
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
</head>
|
||||
<body>
|
||||
<script class="jsrender-template" id="tmpl_connect" type="text/html">
|
||||
<div style="margin-top: 5px;">
|
||||
<div style="display: flex; flex-direction: row; width: 100%; justify-content: space-between">
|
||||
<div style="width: 68%; margin-bottom: 5px">
|
||||
<div>{{tr "Remote address and port:" /}}</div>
|
||||
<input type="text" style="width: 100%" class="connect_address" value="unknown">
|
||||
</div>
|
||||
<div style="width: 20%">
|
||||
<div>{{tr "Server password:" /}}</div>
|
||||
<form name="server_password_form" onsubmit="return false;">
|
||||
<input type="password" id="connect_server_password_{{rnd '0~13377331'/}}" autocomplete="off" style="width: 100%" class="connect_password">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{tr "Nickname:" /}}</div>
|
||||
<input type="text" style="width: 100%" class="connect_nickname" value="">
|
||||
</div>
|
||||
<hr>
|
||||
<div class="group_box">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<div style="text-align: right;">{{tr "Identity Settings" /}}</div>
|
||||
<select class="identity_select">
|
||||
<option name="identity_type" value="TEAFORO">{{tr "Forum Account" /}}</option>
|
||||
<option name="identity_type" value="TEAMSPEAK">{{tr "TeamSpeak" /}}</option>
|
||||
<option name="identity_type" value="NICKNAME">{{tr "Nickname (Debug purposes only!)" /}}</option> <!-- Only available on localhost for debug -->
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="identity_config_TEAMSPEAK identity_config">
|
||||
{{tr "Please enter your exported TS3 Identity string bellow or select your exported Identity"/}}<br>
|
||||
<div style="width: 100%; display: flex; justify-content: stretch; flex-direction: row">
|
||||
<input placeholder="Identity string" style="min-width: 60%; flex-shrink: 1; flex-grow: 2; margin: 5px;" class="identity_string">
|
||||
<div style="max-width: 200px; flex-grow: 1; flex-shrink: 4; margin: 5px"><input style="display: flex; width: 100%;" class="identity_file" type="file"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_TEAFORO identity_config">
|
||||
<div class="connected">
|
||||
{{tr "You're using your forum account as verification"/}}
|
||||
</div>
|
||||
<div class="disconnected">
|
||||
<!-- TODO tr -->
|
||||
You cant use your TeaSpeak forum account.<br>
|
||||
You're not connected!<br>
|
||||
Click {{if !client}}<a href="{{:forum_path}}login.php">here</a>{{else}}<a href="#" class="native-teaforo-login">here</a>{{/if}} to login via the TeaSpeak forum.
|
||||
</div>
|
||||
</div>
|
||||
<div class="identity_config_NICKNAME identity_config">
|
||||
{{tr "This is just for debug and uses the name as unique identifier" /}}
|
||||
{{tr "This is just for debug and uses the name as unique identifier" + "Hello World :D" /}}
|
||||
{{tr "This is just for debug and uses the name as unique identifier" + `Hello World :D 2` /}}
|
||||
</div>
|
||||
|
||||
<div style="background-color: red; border-radius: 1px; display: none" class="error_message"></div>
|
||||
</div> <!-- <a href="<?php echo authPath() . "login.php"; ?>">Login</a> via the TeaSpeak forum. -->
|
||||
</div>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,297 @@
|
|||
import * as ts from "typescript";
|
||||
import * as sha256 from "sha256";
|
||||
import {SyntaxKind} from "typescript";
|
||||
import {TranslationEntry} from "./generator";
|
||||
|
||||
export function generate(file: ts.SourceFile, config: Configuration) : TranslationEntry[] {
|
||||
let result: TranslationEntry[] = [];
|
||||
|
||||
file.forEachChild(n => _generate(config, n, result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function report(node: ts.Node, message: string) {
|
||||
const sf = node.getSourceFile();
|
||||
let { line, character } = sf ? sf.getLineAndCharacterOfPosition(node.getStart()) : {line: -1, character: -1};
|
||||
console.log(`${(sf || {fileName: "unknown"}).fileName} (${line + 1},${character + 1}): ${message}`);
|
||||
}
|
||||
|
||||
function _generate(config: Configuration, node: ts.Node, result: TranslationEntry[]) {
|
||||
//console.log("Node: %s", SyntaxKind[node.kind]);
|
||||
|
||||
call_analize:
|
||||
if(ts.isCallExpression(node)) {
|
||||
const call = <ts.CallExpression>node;
|
||||
const call_name = call.expression["escapedText"] as string;
|
||||
if(call_name != "tr") break call_analize;
|
||||
|
||||
console.dir(call_name);
|
||||
|
||||
console.log("Parameters: %o", call.arguments.length);
|
||||
if(call.arguments.length > 1) {
|
||||
report(call, "Invalid argument count");
|
||||
node.forEachChild(n => _generate(config, n, result));
|
||||
return;
|
||||
}
|
||||
|
||||
const object = <ts.StringLiteral>call.arguments[0];
|
||||
if(object.kind != SyntaxKind.StringLiteral) {
|
||||
report(call, "Invalid argument: " + SyntaxKind[object.kind]);
|
||||
node.forEachChild(n => _generate(config, n, result));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Message: %o", object.text);
|
||||
|
||||
//FIXME
|
||||
if(config.replace_cache) {
|
||||
console.log("Update!");
|
||||
ts.updateCall(call, call.expression, call.typeArguments, [ts.createLiteral("PENIS!")]);
|
||||
}
|
||||
|
||||
const { line, character } = node.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
|
||||
result.push({
|
||||
filename: node.getSourceFile().fileName,
|
||||
line: line,
|
||||
character: character,
|
||||
message: object.text
|
||||
});
|
||||
}
|
||||
|
||||
node.forEachChild(n => _generate(config, n, result));
|
||||
}
|
||||
function create_unique_check(source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] {
|
||||
const nodes: ts.Node[] = [], blocked_nodes: ts.Statement[] = [];
|
||||
|
||||
const node_path = (node: ts.Node) => {
|
||||
const sf = node.getSourceFile();
|
||||
let { line, character } = sf ? sf.getLineAndCharacterOfPosition(node.getStart()) : {line: -1, character: -1};
|
||||
return `${(sf || {fileName: "unknown"}).fileName} (${line + 1},${character + 1})`;
|
||||
};
|
||||
|
||||
const create_error = (variable_name: ts.Expression, variable_path: ts.Expression, other_path: ts.Expression) => {
|
||||
return [
|
||||
ts.createLiteral("Translation with generated name \""),
|
||||
variable_name,
|
||||
ts.createLiteral("\" already exists!\nIt has been already defined here: "),
|
||||
other_path,
|
||||
ts.createLiteral("\nAttempted to redefine here: "),
|
||||
variable_path,
|
||||
ts.createLiteral("\nRegenerate and/or fix your program!")
|
||||
].reduce((a, b) => ts.createBinary(a, SyntaxKind.PlusToken, b));
|
||||
};
|
||||
|
||||
let declarations_file: ts.Expression;
|
||||
const unique_check_label_name = "unique_translation_check";
|
||||
|
||||
/* initialization */
|
||||
{
|
||||
const declarations = ts.createElementAccess(variable, ts.createLiteral("declared"));
|
||||
nodes.push(ts.createAssignment(declarations, ts.createBinary(declarations, SyntaxKind.BarBarToken, ts.createAssignment(declarations, ts.createObjectLiteral()))));
|
||||
|
||||
declarations_file = ts.createElementAccess(variable, ts.createLiteral("declared_files"));
|
||||
nodes.push(ts.createAssignment(declarations_file, ts.createBinary(declarations_file, SyntaxKind.BarBarToken, ts.createAssignment(declarations_file, ts.createObjectLiteral()))));
|
||||
|
||||
variable = declarations;
|
||||
}
|
||||
|
||||
/* test file already loaded */
|
||||
{
|
||||
const unique_id = sha256(source_file.fileName + " | " + (Date.now() / 1000));
|
||||
const property = ts.createElementAccess(declarations_file, ts.createLiteral(unique_id));
|
||||
|
||||
const if_condition = ts.createBinary(property, SyntaxKind.ExclamationEqualsEqualsToken, ts.createIdentifier("undefined"));
|
||||
//
|
||||
let if_then: ts.Block;
|
||||
{
|
||||
const elements: ts.Statement[] = [];
|
||||
|
||||
const console = ts.createIdentifier("console.warn");
|
||||
elements.push(ts.createCall(console, [], [ts.createLiteral("This file has already been loaded!\nAre you executing scripts twice?") as any]) as any);
|
||||
elements.push(ts.createBreak(unique_check_label_name));
|
||||
|
||||
if_then = ts.createBlock(elements);
|
||||
}
|
||||
|
||||
const if_else = ts.createAssignment(property, ts.createLiteral(unique_id));
|
||||
blocked_nodes.push(ts.createIf(if_condition, if_then, if_else as any));
|
||||
}
|
||||
|
||||
/* test if variable has been defined somewhere else */
|
||||
{
|
||||
const for_variable_name = ts.createLoopVariable();
|
||||
const for_variable_path = ts.createLoopVariable();
|
||||
const for_declaration = ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createObjectBindingPattern([
|
||||
ts.createBindingElement(undefined, "name", for_variable_name, undefined),
|
||||
ts.createBindingElement(undefined, "path", for_variable_path, undefined)])
|
||||
, undefined, undefined)]);
|
||||
|
||||
let for_block: ts.Statement;
|
||||
{ //Create the for block
|
||||
const elements: ts.Statement[] = [];
|
||||
|
||||
|
||||
const property = ts.createElementAccess(variable, for_variable_name);
|
||||
const if_condition = ts.createBinary(property, SyntaxKind.ExclamationEqualsEqualsToken, ts.createIdentifier("undefined"));
|
||||
|
||||
//
|
||||
const if_then = ts.createThrow(create_error(for_variable_name, for_variable_path, property));
|
||||
const if_else = ts.createAssignment(property, for_variable_path);
|
||||
const if_valid = ts.createIf(if_condition, if_then, if_else as any);
|
||||
|
||||
elements.push(if_valid);
|
||||
|
||||
for_block = ts.createBlock(elements);
|
||||
}
|
||||
|
||||
let block = ts.createForOf(undefined,
|
||||
for_declaration, ts.createArrayLiteral(
|
||||
[...variables.map(e => ts.createObjectLiteral([
|
||||
ts.createPropertyAssignment("name", ts.createLiteral(e.name)),
|
||||
ts.createPropertyAssignment("path", ts.createLiteral(node_path(e.node)))
|
||||
]))
|
||||
])
|
||||
, for_block);
|
||||
block = ts.addSyntheticLeadingComment(block, SyntaxKind.MultiLineCommentTrivia, "Auto generated helper for testing if the translation keys are unique", true);
|
||||
blocked_nodes.push(block);
|
||||
}
|
||||
return [...nodes, ts.createLabel(unique_check_label_name, ts.createBlock(blocked_nodes))];
|
||||
}
|
||||
|
||||
export function transform(config: Configuration, context: ts.TransformationContext, node: ts.SourceFile) : TransformResult {
|
||||
const cache: VolatileTransformConfig = {} as any;
|
||||
cache.translations = [];
|
||||
|
||||
//Initialize nodes
|
||||
const extra_nodes: ts.Node[] = [];
|
||||
{
|
||||
cache.nodes = {} as any;
|
||||
if(config.use_window) {
|
||||
const window = ts.createIdentifier("window");
|
||||
let translation_map = ts.createPropertyAccess(window, ts.createIdentifier("_translations"));
|
||||
const new_translations = ts.createAssignment(translation_map, ts.createObjectLiteral());
|
||||
|
||||
let translation_map_init: ts.Expression = ts.createBinary(translation_map, ts.SyntaxKind.BarBarToken, new_translations);
|
||||
translation_map_init = ts.createParen(translation_map_init);
|
||||
|
||||
cache.nodes = {
|
||||
translation_map: translation_map,
|
||||
translation_map_init: translation_map_init
|
||||
};
|
||||
} else {
|
||||
const variable_name = "_translations";
|
||||
const variable_map = ts.createIdentifier(variable_name);
|
||||
|
||||
const inline_if = ts.createBinary(ts.createBinary(ts.createTypeOf(variable_map), SyntaxKind.ExclamationEqualsEqualsToken, ts.createLiteral("undefined")), ts.SyntaxKind.BarBarToken, ts.createAssignment(variable_map, ts.createObjectLiteral()));
|
||||
|
||||
cache.nodes = {
|
||||
translation_map: variable_map,
|
||||
translation_map_init: variable_map
|
||||
};
|
||||
|
||||
//ts.createVariableDeclarationList([ts.createVariableDeclaration(variable_name)], ts.NodeFlags.Let)
|
||||
extra_nodes.push(inline_if);
|
||||
}
|
||||
}
|
||||
|
||||
const generated_names: { name: string, node: ts.Node }[] = [];
|
||||
cache.name_generator = (config, node, message) => {
|
||||
const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let name = "";
|
||||
while(name.length < 8) {
|
||||
const char = characters[Math.floor(Math.random() * characters.length)];
|
||||
name = name + char;
|
||||
if(name[0] >= '0' && name[0] <= '9')
|
||||
name = name.substr(1) || "";
|
||||
}
|
||||
|
||||
//FIXME
|
||||
//if(generated_names.indexOf(name) != -1)
|
||||
// return cache.name_generator(config, node, message);
|
||||
|
||||
generated_names.push({name: name, node: node});
|
||||
return name;
|
||||
};
|
||||
|
||||
function visit(node: ts.Node): ts.Node {
|
||||
node = ts.visitEachChild(node, visit, context);
|
||||
return replace_processor(config, cache, node);
|
||||
}
|
||||
node = ts.visitNode(node, visit);
|
||||
extra_nodes.push(...create_unique_check(node, cache.nodes.translation_map_init, generated_names));
|
||||
|
||||
node = ts.updateSourceFileNode(node, [...(extra_nodes as any[]), ...node.statements], node.isDeclarationFile, node.referencedFiles, node.typeReferenceDirectives, node.hasNoDefaultLib, node.referencedFiles);
|
||||
|
||||
const result: TransformResult = {} as any;
|
||||
result.node = node;
|
||||
result.translations = cache.translations;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function replace_processor(config: Configuration, cache: VolatileTransformConfig, node: ts.Node) : ts.Node {
|
||||
if(config.verbose)
|
||||
console.log("Process %s", SyntaxKind[node.kind]);
|
||||
if(ts.isCallExpression(node)) {
|
||||
const call = <ts.CallExpression>node;
|
||||
const call_name = call.expression["escapedText"] as string;
|
||||
if(call_name != "tr") return node;
|
||||
|
||||
if(config.verbose) {
|
||||
console.dir(call_name);
|
||||
console.log("Parameters: %o", call.arguments.length);
|
||||
}
|
||||
if(call.arguments.length > 1) {
|
||||
report(call, "Invalid argument count");
|
||||
return node;
|
||||
}
|
||||
|
||||
const object = <ts.StringLiteral>call.arguments[0];
|
||||
if(object.kind != SyntaxKind.StringLiteral) {
|
||||
report(call, "Invalid argument: " + SyntaxKind[object.kind]);
|
||||
return node;
|
||||
}
|
||||
|
||||
if(config.verbose)
|
||||
console.log("Message: %o", object.text || object.getText());
|
||||
|
||||
const variable_name = ts.createIdentifier(cache.name_generator(config, node, object.text || object.getText()));
|
||||
const variable_init = ts.createPropertyAccess(cache.nodes.translation_map_init, variable_name);
|
||||
|
||||
const variable = ts.createPropertyAccess(cache.nodes.translation_map, variable_name);
|
||||
const new_variable = ts.createAssignment(variable, call);
|
||||
|
||||
const source_file = node.getSourceFile();
|
||||
let { line, character } = source_file ? source_file.getLineAndCharacterOfPosition(node.getStart()) : {line: -1, character: -1};
|
||||
|
||||
cache.translations.push({
|
||||
message: object.text || object.getText(),
|
||||
line: line,
|
||||
character: character,
|
||||
filename: (source_file || {fileName: "unknown"}).fileName
|
||||
});
|
||||
|
||||
return ts.createBinary(variable_init, ts.SyntaxKind.BarBarToken, new_variable);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
export interface Configuration {
|
||||
use_window?: boolean;
|
||||
replace_cache?: boolean;
|
||||
verbose?: boolean;
|
||||
}
|
||||
|
||||
export interface TransformResult {
|
||||
node: ts.SourceFile;
|
||||
translations: TranslationEntry[];
|
||||
}
|
||||
|
||||
interface VolatileTransformConfig {
|
||||
nodes: {
|
||||
translation_map: ts.Expression;
|
||||
translation_map_init: ts.Expression;
|
||||
};
|
||||
|
||||
name_generator: (config: Configuration, node: ts.Node, message: string) => string;
|
||||
translations: TranslationEntry[];
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
import * as ts from "typescript";
|
||||
import * as generator from "./generator";
|
||||
import * as ts_generator from "./ts_generator";
|
||||
import * as path from "path";
|
||||
import * as mkdirp from "mkdirp";
|
||||
|
||||
import {PluginConfig} from "ttypescript/lib/PluginCreator";
|
||||
import {writeFileSync} from "fs";
|
||||
import {TranslationEntry} from "./generator";
|
||||
|
||||
interface Config {
|
||||
target_file?: string;
|
||||
|
@ -13,25 +17,30 @@ let process_config: Config;
|
|||
export default function(program: ts.Program, config?: PluginConfig) : (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => ts.SourceFile {
|
||||
process_config = config as any || {};
|
||||
|
||||
if(process_config.verbose)
|
||||
const base_path = path.dirname(program.getCompilerOptions().project || program.getCurrentDirectory());
|
||||
if(process_config.verbose) {
|
||||
console.log("TRGen transformer called");
|
||||
console.log("Base path: %s", base_path);
|
||||
}
|
||||
|
||||
process.on('exit', function () {
|
||||
const target = path.isAbsolute(process_config.target_file) ? process_config.target_file : path.join(base_path, process_config.target_file);
|
||||
if(process_config.target_file) {
|
||||
if(process_config.verbose)
|
||||
console.log("Writing translation file to " + process_config.target_file);
|
||||
console.log("Writing translation file to " + target);
|
||||
|
||||
writeFileSync(process_config.target_file, JSON.stringify(translations, null, 2));
|
||||
mkdirp.sync(path.dirname(target));
|
||||
writeFileSync(target, JSON.stringify(translations, null, 2));
|
||||
}
|
||||
});
|
||||
|
||||
return ctx => transformer(ctx);
|
||||
}
|
||||
|
||||
const translations: generator.TranslationEntry[] = [];
|
||||
const translations: TranslationEntry[] = [];
|
||||
const transformer = (context: ts.TransformationContext) => (rootNode: ts.SourceFile) => {
|
||||
console.log("Processing " + rootNode.fileName);
|
||||
const result = generator.transform({
|
||||
const result = ts_generator.transform({
|
||||
use_window: false,
|
||||
replace_cache: true
|
||||
}, context, rootNode);
|
||||
|
|
|
@ -75,6 +75,14 @@
|
|||
"path" => "wasm/",
|
||||
"local-path" => "./asm/generated/"
|
||||
],
|
||||
[ /* translations */
|
||||
"type" => "i18n",
|
||||
"search-pattern" => "/.*\.(translation|json)/",
|
||||
"build-target" => "dev|rel",
|
||||
|
||||
"path" => "i18n/",
|
||||
"local-path" => "./shared/i18n/"
|
||||
],
|
||||
|
||||
/* vendors */
|
||||
[
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"compile-sass": "sass --update .:.",
|
||||
"build-worker": "tsc -p shared/js/workers/tsconfig_worker_codec.json",
|
||||
"dtsgen": "node build/dtsgen/index.js",
|
||||
"trgen": "node build/trgen/index.js"
|
||||
"trgen": "node build/trgen/index.js",
|
||||
"ttsc": "ttsc"
|
||||
},
|
||||
"author": "TeaSpeak (WolverinDEV)",
|
||||
"license": "ISC",
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
"""
|
||||
We want python 2.7 again...
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
"""
|
||||
from googletrans import Translator # Use the free webhook
|
||||
def run_translate(messages, source_language, target_language):
|
||||
translator = Translator()
|
||||
_translations = translator.translate(messages, src=source_language, dest=target_language)
|
||||
result = []
|
||||
for translation in _translations:
|
||||
result.append({
|
||||
"source": translation.origin,
|
||||
"translated": translation.text
|
||||
})
|
||||
return result
|
||||
"""
|
||||
|
||||
|
||||
from google.cloud import translate # Use googles could solution
|
||||
def run_translate(messages, source_language, target_language):
|
||||
translate_client = translate.Client()
|
||||
|
||||
# The text to translate
|
||||
text = u'Hello, world!'
|
||||
# The target language
|
||||
|
||||
result = []
|
||||
limit = 16
|
||||
for chunk in [messages[i:i + limit] for i in xrange(0, len(messages), limit)]:
|
||||
# Translates some text into Russian
|
||||
print("Requesting {} translations".format(len(chunk)))
|
||||
translations = translate_client.translate(chunk, target_language=target_language)
|
||||
|
||||
for translation in translations:
|
||||
result.append({
|
||||
"source": translation["input"],
|
||||
"translated": translation["translatedText"]
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def translate_messages(source, destination, target_language):
|
||||
with open(source) as f:
|
||||
data = json.load(f)
|
||||
|
||||
result = {}
|
||||
try:
|
||||
with open(destination) as f:
|
||||
result = json.load(f)
|
||||
print("loaded old result")
|
||||
except:
|
||||
pass
|
||||
|
||||
translations = result["translations"]
|
||||
if translations is None:
|
||||
print("Using new translation map")
|
||||
translations = []
|
||||
else:
|
||||
print("Loaded {} old translations".format(len(translations)))
|
||||
# TODO translate
|
||||
|
||||
messages = []
|
||||
for message in data:
|
||||
try:
|
||||
messages.index(message["message"])
|
||||
except:
|
||||
try:
|
||||
found = False
|
||||
for entry in translations:
|
||||
if entry["key"]["message"] == message["message"]:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
raise Exception('add message for translate')
|
||||
except:
|
||||
messages.append(message["message"])
|
||||
|
||||
print("Translating {} messages".format(len(messages)))
|
||||
if len(messages) != 0:
|
||||
_translations = run_translate(messages, 'en', target_language)
|
||||
print("Messages translated, generating target file")
|
||||
|
||||
for translation in _translations:
|
||||
translations.append({
|
||||
"key": {
|
||||
"message": translation["source"]
|
||||
},
|
||||
"translated": translation["translated"],
|
||||
"flags": [
|
||||
"google-translate"
|
||||
]
|
||||
})
|
||||
|
||||
print("Writing target file")
|
||||
result["translations"] = translations
|
||||
if result["info"] is None:
|
||||
result["info"] = {
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Google Translate, via script by Markus Hadenfeldt",
|
||||
"email": "gtr.i18n.client@teaspeak.de"
|
||||
}
|
||||
],
|
||||
"name": "Auto translated messages for language " + target_language
|
||||
}
|
||||
|
||||
with open(destination, 'w') as f:
|
||||
f.write(json.dumps(result, indent=2))
|
||||
print("Done")
|
||||
|
||||
|
||||
def main():
|
||||
#print(run_translate(["Hello World", "Bla bla bla", "Im a unicorn"], "en", "de"))
|
||||
translate_messages("generated/messages_script.json", "test.json", "de")
|
||||
translate_messages("generated/messages_template.json", "test.json", "de")
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
BASEDIR=$(dirname "$0")
|
||||
cd "$BASEDIR"
|
||||
|
||||
#Generate the script translations
|
||||
npm run ttsc -- -p $(pwd)/tsconfig/tsconfig.json
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to generate translation file for the script files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npm run trgen -- -f $(pwd)/html/templates.html -d $(pwd)/generated/messages_template.json
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to generate translations file for the template files"
|
||||
exit 1
|
||||
fi
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
This should be executed as python 2.7 (because of pydub)
|
||||
This should be executed with python 2.7 (because of pydub)
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
|
@ -25,7 +26,7 @@ def tts(text, file):
|
|||
'Chrome/69.0.3497.100 Safari/537.36 OPR/56.0.3051.52',
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
'referer': 'https://www.naturalreaders.com/online/',
|
||||
'authority': 'kfiuqykx63.execute-api.us-east-1.amazonaws.com'
|
||||
'authority': 'kfiuqykx63.execute-api.us-east-1.amazonaws.com' #You may need to change that here
|
||||
},
|
||||
data=json.dumps({"t": text})
|
||||
)
|
||||
|
|
|
@ -990,8 +990,8 @@
|
|||
<a>{{tr "Nickname"/}}</a>
|
||||
<div class="help-tip tip-right tip-small" checked>
|
||||
<p>
|
||||
{{tr "Bans the client by his current nickname.<br>
|
||||
The currently nickname cant be used until the ban expired"/}}
|
||||
{{tr "Bans the client by his current nickname.<br>" +
|
||||
"The currently nickname cant be used until the ban expired"/}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1000,11 +1000,11 @@
|
|||
<a>{{tr "Hardware ID" /}}</a>
|
||||
<div class="help-tip tip-right tip-small">
|
||||
<p>
|
||||
{{tr "Bans the client by his hardware id.<br>
|
||||
The hardware id has different meanings, depends on the users agent<br>
|
||||
TeaClient: The hardware id will be equal to the mac address<br>
|
||||
TeaWeb: The TeaSpeak web client hasn't a hardware id, it will be random<br>
|
||||
TeamSpeak 3 client: The hardware id will be a result of some hashes from hardware specific properties" /}}
|
||||
{{tr "Bans the client by his hardware id.<br>" +
|
||||
"The hardware id has different meanings, depends on the users agent<br>" +
|
||||
"TeaClient: The hardware id will be equal to the mac address<br>" +
|
||||
"TeaWeb: The TeaSpeak web client hasn't a hardware id, it will be random<br>" +
|
||||
"TeamSpeak 3 client: The hardware id will be a result of some hashes from hardware specific properties" /}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1087,10 +1087,10 @@
|
|||
<a>{{tr "Use this ban as a global ban" /}}</a>
|
||||
<div class="help-tip tip-center tip-small">
|
||||
<p>
|
||||
{{tr "Global bans are bans which apply instance wide.<br>
|
||||
This means that (if this rule apply to a victim) cant join <b>any</b> virtual server!<br>
|
||||
Global bans are by default shown to every server admin group,<br>
|
||||
but could only be created with query rights"/}}
|
||||
{{tr "Global bans are bans which apply instance wide.<br>" +
|
||||
"This means that (if this rule apply to a victim) cant join <b>any</b> virtual server!<br>" +
|
||||
"Global bans are by default shown to every server admin group,<br>" +
|
||||
"but could only be created with query rights"/}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"info": {
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Markus Hadenfeldt",
|
||||
"email": "i18n.client@teaspeak.de"
|
||||
}
|
||||
],
|
||||
"name": "German translations"
|
||||
},
|
||||
"translations": [
|
||||
{
|
||||
"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"
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": {
|
||||
"message": "Create a new connection"
|
||||
},
|
||||
"translated": "Verbinden",
|
||||
"flags": [ ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"translations": [
|
||||
{
|
||||
"key": "de_DE",
|
||||
"path": "de_DE.translation"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"info": {
|
||||
"contributors": [
|
||||
/* add yourself if you have done anything :) */
|
||||
{
|
||||
"name": "Markus Hadenfeldt", /* this field is required */
|
||||
"email": "i18n.client@teaspeak.de" /* this field is required */
|
||||
}
|
||||
],
|
||||
"name": "A template translation file" /* this field is required */
|
||||
},
|
||||
"translations": [ /* Array with all translation objects */
|
||||
{ /* translation object */
|
||||
"key": { /* the key */
|
||||
"message": "Show permission description", /* necessary to identify the message */
|
||||
"line": 374, /* optional, only for specify the translation for a specific case (Not supported yet!) */
|
||||
"character": 30, /* optional, only for specify the translation for a specific case (Not supported yet!) */
|
||||
"filename": "/home/wolverindev/TeaSpeak/TeaSpeak/Web-Client/shared/js/ui/modal/ModalPermissionEdit.ts" /* optional, only for specify the translation for a specific case (Not supported yet!) */
|
||||
},
|
||||
"translated": "Berechtigungsbeschreibung anzeigen", /* The actual translation */
|
||||
"flags": [ /* some flags for this translation */
|
||||
"google-translate", /* this translation has been made with google translator */
|
||||
"verified" /* this translation has been verified by a native speaker */
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,124 @@
|
|||
/*
|
||||
"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"
|
||||
]
|
||||
*/
|
||||
namespace i18n {
|
||||
interface TranslationKey {
|
||||
message: string;
|
||||
line?: number;
|
||||
character?: number;
|
||||
filename?: string;
|
||||
}
|
||||
|
||||
interface Translation {
|
||||
key: TranslationKey;
|
||||
translated: string;
|
||||
flags?: string[];
|
||||
}
|
||||
|
||||
interface Contributor {
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface FileInfo {
|
||||
name: string;
|
||||
contributors: Contributor[];
|
||||
}
|
||||
|
||||
interface TranslationFile {
|
||||
info: FileInfo;
|
||||
translations: Translation[];
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
console.log("Translating \"%s\". Default: \"%s\"", key, message);
|
||||
|
||||
return message;
|
||||
let translated = message;
|
||||
for(const translation of translations) {
|
||||
if(translation.key.message == message) {
|
||||
translated = translation.translated;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fast_translate[message] = translated;
|
||||
return translated;
|
||||
}
|
||||
|
||||
export function load_file(url: string) : Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
$.ajax({
|
||||
url: url,
|
||||
async: true,
|
||||
success: result => {
|
||||
console.dir(result);
|
||||
const file = (typeof(result) === "string" ? JSON.parse(result) : result) as TranslationFile;
|
||||
if(!file) {
|
||||
reject("Invalid json");
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO validate file
|
||||
translations = file.translations;
|
||||
log.info(LogCategory.I18N, tr("Successfully initialized up translation file from %s"), url);
|
||||
resolve();
|
||||
},
|
||||
error: (xhr, error) => {
|
||||
log.warn(LogCategory.I18N, "Failed to load translation file from \"%s\". Error: %o", url, error);
|
||||
reject("Failed to load file: " + error);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
export async function initialize() {
|
||||
// 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;
|
||||
|
||||
const tr: typeof i18n.tr = i18n.tr;
|
||||
|
||||
/*
|
||||
{
|
||||
"info": {
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Markus Hadenfeldt",
|
||||
"email": "i18n.client@teaspeak.de"
|
||||
}
|
||||
],
|
||||
"name": "German translations"
|
||||
},
|
||||
"translations": [
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
|
@ -5,7 +5,8 @@ enum LogCategory {
|
|||
PERMISSIONS,
|
||||
GENERAL,
|
||||
NETWORKING,
|
||||
VOICE
|
||||
VOICE,
|
||||
I18N
|
||||
}
|
||||
|
||||
namespace log {
|
||||
|
@ -17,7 +18,6 @@ namespace log {
|
|||
ERROR
|
||||
}
|
||||
|
||||
//TODO add translation
|
||||
let category_mapping = new Map<number, string>([
|
||||
[LogCategory.CHANNEL, "Channel "],
|
||||
[LogCategory.CLIENT, "Client "],
|
||||
|
@ -25,7 +25,8 @@ namespace log {
|
|||
[LogCategory.PERMISSIONS, "Permission "],
|
||||
[LogCategory.GENERAL, "General "],
|
||||
[LogCategory.NETWORKING, "Network "],
|
||||
[LogCategory.VOICE, "Voice "]
|
||||
[LogCategory.VOICE, "Voice "],
|
||||
[LogCategory.I18N, "I18N "]
|
||||
]);
|
||||
|
||||
function logDirect(type: LogType, message: string, ...optionalParams: any[]) {
|
||||
|
|
|
@ -91,16 +91,34 @@ function setup_jsrender() : boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
function main() {
|
||||
async function initialize() {
|
||||
if(!setup_jsrender()) return;
|
||||
|
||||
//http://localhost:63343/Web-Client/index.php?_ijt=omcpmt8b9hnjlfguh8ajgrgolr&default_connect_url=true&default_connect_type=teamspeak&default_connect_url=localhost%3A9987&disableUnloadDialog=1&loader_ignore_age=1
|
||||
AudioController.initializeAudioController();
|
||||
if(!TSIdentityHelper.setup()) {
|
||||
console.error(tr( "Could not setup the TeamSpeak identity parser!"));
|
||||
try {
|
||||
await i18n.initialize();
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to initialized the translation system!\nError: %o"), error);
|
||||
displayCriticalError("Failed to setup the translation system");
|
||||
return;
|
||||
}
|
||||
|
||||
AudioController.initializeAudioController();
|
||||
if(!TSIdentityHelper.setup()) {
|
||||
console.error(tr("Could not setup the TeamSpeak identity parser!"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ppt.initialize();
|
||||
} catch(error) {
|
||||
console.error(tr("Failed to initialize ppt!\nError: %o"), error);
|
||||
displayCriticalError(tr("Failed to initialize ppt!"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
//http://localhost:63343/Web-Client/index.php?_ijt=omcpmt8b9hnjlfguh8ajgrgolr&default_connect_url=true&default_connect_type=teamspeak&default_connect_url=localhost%3A9987&disableUnloadDialog=1&loader_ignore_age=1
|
||||
|
||||
settings = new Settings();
|
||||
globalClient = new TSClient();
|
||||
/** Setup the XF forum identity **/
|
||||
|
@ -150,11 +168,6 @@ function main() {
|
|||
}
|
||||
}
|
||||
|
||||
ppt.initialize().catch(error => {
|
||||
console.error(tr("Failed to initialize ppt!"));
|
||||
//TODO error notification?
|
||||
});
|
||||
|
||||
/*
|
||||
let tag = $("#tmpl_music_frame").renderTag({
|
||||
//thumbnail: "img/loading_image.svg"
|
||||
|
@ -183,8 +196,9 @@ function main() {
|
|||
});
|
||||
}
|
||||
|
||||
app.loadedListener.push(() => {
|
||||
app.loadedListener.push(async () => {
|
||||
try {
|
||||
await initialize();
|
||||
main();
|
||||
if(!audio.player.initialized()) {
|
||||
log.info(LogCategory.VOICE, tr("Initialize audio controller later!"));
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,7 +8,7 @@
|
|||
{
|
||||
"transform": "../../build/trgen/ttsc_transformer.js",
|
||||
"type": "program",
|
||||
"target_file": "../generated/i18n.translation",
|
||||
"target_file": "../generated/messages_script.json",
|
||||
"verbose": true
|
||||
}
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue