This commit is contained in:
Christian Fraß 2020-04-04 14:10:53 +02:00
parent 8f97d6326c
commit 1d7bf5a08b
5 changed files with 320 additions and 14 deletions

126
app/cache.js Normal file
View file

@ -0,0 +1,126 @@
export /*abstract */class Cache {
/**
* @param {string} key
* @return {boolean}
* @author fenris
*/
/*protected */has(key) {
throw (new Error('not implemented'));
}
/**
* @param {string} key
* @return {any}
* @author fenris
*/
/*protected */fetch(key) {
throw (new Error('not implemented'));
}
/**
* @param {string} key
* @param {any} value
* @author fenris
*/
/*protected */store(key, value) {
throw (new Error('not implemented'));
}
/**
* @param {string} key
* @param {()=>Promise<any>} retrieve
* @return {Promise<any>}
* @author fenris
*/
/*public */async get(key, retrieve) {
if (this.has(key)) {
const value = this.fetch(key);
return Promise.resolve(value);
} else {
const value = await retrieve();
this.store(key, value);
return Promise.resolve(value);
}
}
}
/**
* @author fenris
*/
class CacheNone extends Cache {
/**
* @author fenris
*/
/*public */constructor() {
super();
}
/**
* @author fenris
*/
/*protected */has(key) {
return false;
}
/**
* @author fenris
*/
/*protected */fetch(key) {
throw (new Error('not possible'));
}
/**
* @author fenris
*/
/*protected */store(key, value) {
}
}
/**
* @author fenris
*/
export class CacheLocalstorage extends Cache {
/**
* @param {string} [corner] for separating the cache instance from others
* @author fenris
*/
/*public */constructor(corner = null) {
super();
this.corner = corner;
}
/**
* @author fenris
*/
/*private */augmentKey(key) {
return ((this.corner === null) ? key : (this.corner + '/' + key));
}
/**
* @author fenris
*/
/*protected */has(key) {
return (window.localStorage.getItem(this.augmentKey(key)) !== null);
}
/**
* @author fenris
*/
/*protected */fetch(key) {
const valueRaw = window.localStorage.getItem(this.augmentKey(key));
const value = JSON.parse(valueRaw);
return value;
}
/**
* @author fenris
*/
/*protected */store(key, value) {
const valueRaw = JSON.stringify(value);
window.localStorage.setItem(this.augmentKey(key), valueRaw);
}
}

40
app/file.js Normal file
View file

@ -0,0 +1,40 @@
/**
* @param {string} path
* @return Promise<string>
* @todo use Util.fetch instead?
* @author fenris
*/
export async function read (path) {
return (
new Promise(
(resolve, reject) => {
let request = new XMLHttpRequest();
request.open('GET', '/' + path, true);
request.onreadystatechange = () => {
switch (request.readyState) {
case XMLHttpRequest.DONE: {
switch (request.status) {
case 0: {
reject(new Error('XMLHttpRequest failed'));
break;
}
default: {
resolve(request.responseText);
break;
}
}
break;
}
default: {
console.warn('unhandled readyState "' + request.readyState + '"');
break;
}
}
};
request.send(null);
}
)
);
}

View file

@ -11,6 +11,7 @@ import _dompurify from 'dompurify'
import keyboardjs from 'keyboardjs' import keyboardjs from 'keyboardjs'
import { ContinuousVoiceHandler, PushToTalkVoiceHandler, VADVoiceHandler, initVoice } from './voice' import { ContinuousVoiceHandler, PushToTalkVoiceHandler, VADVoiceHandler, initVoice } from './voice'
import {initialize as localizationInitialize, translate} from './loc';
const dompurify = _dompurify(window) const dompurify = _dompurify(window)
@ -1013,7 +1014,13 @@ function userToState () {
var voiceHandler var voiceHandler
var testVoiceHandler var testVoiceHandler
initVoice(data => { function translateEverything() {
}
async function main() {
await localizationInitialize(navigator.language);
translateEverything();
initVoice(data => {
if (testVoiceHandler) { if (testVoiceHandler) {
testVoiceHandler.write(data) testVoiceHandler.write(data)
} }
@ -1025,6 +1032,10 @@ initVoice(data => {
} else if (voiceHandler) { } else if (voiceHandler) {
voiceHandler.write(data) voiceHandler.write(data)
} }
}, err => { }, err => {
log('Cannot initialize user media. Microphone will not work:', err) log('Cannot initialize user media. Microphone will not work:', err)
}) })
}
main();

121
app/loc.js Normal file
View file

@ -0,0 +1,121 @@
import {CacheLocalstorage} from './cache';
import {read as fileRead} from './file';
// import {Util} from 'util';
/**
* the relative path to the directory containing the JSON localization files
*
* @var {string}
* @author fenris
*/
var _directory = 'loc';
/**
* the default language to use
*
* @var {string}
* @author fenris
*/
var _languageDefault = null;
/**
* the fallback language to use
*
* @var {string}
* @author fenris
*/
var _languageFallback = null;
/**
* @var {Cache}
* @author fenris
*/
var _cache = null;
/**
* two level map with ISO-639-1 code as first key and translation id as second key
*
* @var {Map<string,Map<string,string>>}
* @author fenris
*/
var _data = {};
/**
* @param {string} language
* @return Promise<Map<string,string>>
* @author fenris
*/
async function retrieveData (language) {
const regexp = (new RegExp("^([a-z]{2})$"));
if (regexp.exec(language) === null) {
return Promise.reject(new Error('invalid language code "' + language + '"'));
} else {
const path = (_directory + '/' + language + '.json');
let content;
try {
content = await fileRead(path);
} catch (exception) {
return Promise.reject(new Error('could not load localization data for language "' + language + '": ' + error.toString()));
}
let data;
try {
data = JSON.parse(content);
} catch (exception) {
return Promise.reject(new Error('invalid JSON localization data for language "' + language + '": ' + exception.toString()));
}
return Promise.resolve(data);
}
}
/**
* @param {string} languageDefault
* @param {string} [languageFallback]
* @author fenris
*/
export async function initialize (languageDefault, languageFallback = 'en') {
_cache = new CacheLocalstorage('loc');
_languageFallback = languageFallback;
_languageDefault = languageDefault;
for (const language of [_languageFallback, _languageDefault]) {
if (_data.hasOwnProperty(language)) continue;
console.log('--', 'loading localization data for language "' + language + '" ...');
let data;
try {
data = await _cache.get(language, () => retrieveData(language));
} catch (exception) {
console.warn(exception.toString());
}
_data[language] = data;
}
}
/**
* gets a translation by its key for a specific language
*
* @param {string} key
* @param {string} [languageChosen]
* @return {string}
* @author fenris
*/
export function translate (key, languageChosen = _languageDefault) {
let result = undefined;
for (const language of [languageChosen, _languageFallback]) {
if (_data.hasOwnProperty(language) && (_data[language] !== undefined) && _data[language].hasOwnProperty(key)) {
result = _data[language][key];
break;
}
}
if (result === undefined) {
result = ('{{' + key + '}}');
}
return result;
}

8
tools/build Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env sh
npm run build
## loc
mkdir -p dist/loc
cp -ruv loc/* dist/loc/