i18 can be dynamically loaded from json file
This commit is contained in:
parent
a854bb0eb3
commit
8614375f9c
@ -11,11 +11,11 @@ npm run build
|
||||
* jsx syntax
|
||||
* i18n
|
||||
* event -> command
|
||||
* controller -> view injection
|
||||
|
||||
#### TODO
|
||||
merge with this repo
|
||||
* [ ] history
|
||||
* [ ] model -> controller to view injection
|
||||
|
||||
add example
|
||||
* [ ] http
|
||||
|
@ -4,6 +4,5 @@
|
||||
["@babel/plugin-transform-react-jsx", {
|
||||
"pragma": "huh.eh"
|
||||
}],
|
||||
["@babel/plugin-proposal-class-properties"]
|
||||
]
|
||||
}
|
||||
|
16
example/parcel-babel/assets/i18/en.x
Normal file
16
example/parcel-babel/assets/i18/en.x
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"data": {
|
||||
"test.btn": "Click me!",
|
||||
"language.label": "Language:",
|
||||
"h1.hello": "Hello",
|
||||
"alert.hi": {
|
||||
"txt": "hi {{click.count}}",
|
||||
"var": [
|
||||
{
|
||||
"key": "click.count",
|
||||
"model": "alert.button"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
16
example/parcel-babel/assets/i18/pl.x
Normal file
16
example/parcel-babel/assets/i18/pl.x
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"data": {
|
||||
"test.btn": "Kliknij!",
|
||||
"language.label": "Język:",
|
||||
"h1.hello": "Witaj",
|
||||
"alert.hi": {
|
||||
"txt": "Cześć {{click.count}}",
|
||||
"var": [
|
||||
{
|
||||
"key": "click.count",
|
||||
"model": "alert.button"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,55 +1,87 @@
|
||||
import huh, {Random, I18, HFacade, HEvent} from '@szczepano/huh'
|
||||
import huh, {Random, I18, HFacade, HEvent, HCtrl, HInjection, HModel} from '@szczepano/huh'
|
||||
|
||||
class Hello {
|
||||
// I18n
|
||||
I18.registerLanguage('en', {
|
||||
path:'assets/i18/en.x'
|
||||
});
|
||||
I18.registerLanguage('pl', {
|
||||
path:'assets/i18/pl.x'
|
||||
});
|
||||
|
||||
// View
|
||||
|
||||
class HelloView {
|
||||
render() {
|
||||
return <h1 h-i18="h1.hello">Hello</h1>
|
||||
}
|
||||
}
|
||||
|
||||
const AlertHiCmd = () => {
|
||||
alert(I18.getKey('alert.hi'));
|
||||
class AlertButtonView {
|
||||
render() {
|
||||
return <button h-i18="test.btn" h-ctrl="alert.button">Click me!</button>
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageButton {
|
||||
constructor(language) {
|
||||
this.language = language
|
||||
}
|
||||
render() {
|
||||
return <button h-ctrl="language.button">{this.language}</button>
|
||||
}
|
||||
}
|
||||
|
||||
const start = () => {
|
||||
const title = new Hello()
|
||||
const arr = ['w','o','r','l','d']
|
||||
const components = []
|
||||
console.log(Random.intRange(0, 10))
|
||||
const components = [];
|
||||
arr.forEach((el) => {
|
||||
components.push(<span>{el}</span>)
|
||||
})
|
||||
I18.registerLanguage('en', {
|
||||
data: {
|
||||
'alert.hi': 'hi',
|
||||
}
|
||||
})
|
||||
I18.registerLanguage('pl', {
|
||||
data: {
|
||||
'test.btn':'Kliknij!',
|
||||
'language.label': 'Język:',
|
||||
'h1.hello': 'Witaj',
|
||||
'alert.hi': 'Cześć',
|
||||
}
|
||||
})
|
||||
const handleLanguageClick = (e) => {
|
||||
I18.switchLanguage(e.target.innerText);
|
||||
}
|
||||
const btn = <button h-i18="test.btn" onclick={() => dispatchEvent(new HEvent('alert.hi'))}>Click me!</button>
|
||||
const langs = ['en', 'pl']
|
||||
langs.forEach((l, i) => {
|
||||
langs[i] = <button onclick={handleLanguageClick}>{l}</button>
|
||||
const langs = []
|
||||
new Array('en', 'pl').forEach((l, i) => {
|
||||
langs.push(new LanguageButton(l));
|
||||
})
|
||||
return (<div>
|
||||
<div>
|
||||
<label h-i18="language.label">Language:</label>{langs}
|
||||
<label h-i18="language.label">Language:</label>
|
||||
{langs}
|
||||
</div>
|
||||
{title}
|
||||
{new HelloView()}
|
||||
{components}
|
||||
<br />
|
||||
{btn}
|
||||
{new AlertButtonView()}
|
||||
{new AlertButtonView()}
|
||||
</div>)
|
||||
}
|
||||
document.getElementById('main').appendChild(start())
|
||||
|
||||
// model
|
||||
class AlertButtonModel extends HModel {
|
||||
constructor () {
|
||||
super({
|
||||
'click.count': 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// controller
|
||||
HCtrl.add('language.button', null, [
|
||||
new HInjection('click', (e) => {
|
||||
I18.switchLanguage(e.target.innerText);
|
||||
}),
|
||||
]);
|
||||
HCtrl.add('alert.button', new AlertButtonModel(), [
|
||||
new HInjection('click', () => {
|
||||
dispatchEvent(new HEvent('alert.hi'));
|
||||
}),
|
||||
]);
|
||||
|
||||
// command
|
||||
const AlertHiCmd = () => {
|
||||
const model = HCtrl.model('alert.button');
|
||||
const count = model.get('click.count');
|
||||
model.set('click.count', count + 1);
|
||||
alert(I18.getKey('alert.hi', model));
|
||||
}
|
||||
HFacade.register('alert.hi', AlertHiCmd);
|
||||
// hack to assign data to all components
|
||||
I18.switchLanguage('en');
|
||||
I18.loadLanguage('en');
|
||||
document.getElementById('main').appendChild(start())
|
||||
|
@ -5,18 +5,18 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "parcel index.html"
|
||||
"dev": "parcel index.html assets/**/*",
|
||||
"build": "parcel build index.html assets/**/*"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.1",
|
||||
"@babel/plugin-transform-react-jsx": "^7.10.3",
|
||||
"@babel/preset-env": "^7.10.3",
|
||||
"parcel-bundler": "^1.12.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@szczepano/huh": "0.0.4"
|
||||
"@szczepano/huh": "0.0.3"
|
||||
}
|
||||
}
|
||||
|
221
huh.js
221
huh.js
@ -2,9 +2,11 @@
|
||||
export default class huh {
|
||||
static eh(tag, data, ...children) {
|
||||
let el = document.createElement(tag);
|
||||
if(data && 'h-i18' in data) {
|
||||
I18.register(data['h-i18'], el);
|
||||
if(data) {
|
||||
if('h-i18' in data) I18.register(data['h-i18'], el);
|
||||
if('h-ctrl' in data) HCtrl.register(data['h-ctrl'], el);
|
||||
}
|
||||
|
||||
for(let arg in data) {
|
||||
if(typeof data[arg] == "function") {
|
||||
el[arg] = data[arg];
|
||||
@ -78,46 +80,6 @@ export const HTTP = (o) => {
|
||||
});
|
||||
}
|
||||
|
||||
// Model
|
||||
let _minstance = null
|
||||
|
||||
export class Model {
|
||||
constructor() {
|
||||
if(_minstance) {
|
||||
throw new Error('Model is Singleton')
|
||||
} else {
|
||||
this._data = {}
|
||||
this._pub = Pub.instance()
|
||||
_minstance = this
|
||||
}
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this._data[key] = value
|
||||
this._pub.send(new Sub(`model.${key}`, {type:'set', value}))
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this._data[key]
|
||||
}
|
||||
|
||||
update(key, value) {
|
||||
if(!(key in this._data)) throw new Error(`Invalid key ${key}`)
|
||||
this._data[key] = value
|
||||
this._pub.send(new Sub(`model.${key}`, {type:'update', value}))
|
||||
}
|
||||
|
||||
del(key) {
|
||||
delete this._data[key]
|
||||
this._pub.send(new Sub(`model.${key}`, {type:'del', value}))
|
||||
}
|
||||
|
||||
static instance() {
|
||||
if (!_minstance) _minstance = new Model()
|
||||
return _minstance
|
||||
}
|
||||
}
|
||||
|
||||
export class Random {
|
||||
static intRange(min, max) {
|
||||
min = Math.ceil(min);
|
||||
@ -147,7 +109,7 @@ export class Random {
|
||||
|
||||
// Event -> Command pattern
|
||||
|
||||
export class HEvent extends Event{
|
||||
export class HEvent extends Event {
|
||||
constructor(type, data) {
|
||||
super(type);
|
||||
this.data = data;
|
||||
@ -185,11 +147,91 @@ export class HFacade {
|
||||
_hlisteners[name] = new CmdWrapper(cmd, once, chain);
|
||||
addEventListener(name, _hlisteners[name].handler);
|
||||
}
|
||||
|
||||
static initialize(data) {
|
||||
data.forEach((el) => {
|
||||
_hlisteners[el.name] = new CmdWrapper(el.cmd, el.once, el.chain);
|
||||
addEventListener(el.name, _hlisteners[el.name].handler);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// View -> Model -> Controller
|
||||
|
||||
export class HModel {
|
||||
constructor(predefined) {
|
||||
this._data = Object.assign({}, predefined);
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this._data[key] = value
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this._data[key]
|
||||
}
|
||||
|
||||
update(key, value) {
|
||||
if(!(key in this._data)) throw new Error(`Invalid key ${key}`)
|
||||
this._data[key] = value
|
||||
}
|
||||
|
||||
del(key) {
|
||||
delete this._data[key]
|
||||
}
|
||||
}
|
||||
|
||||
export class HInjection {
|
||||
constructor(name, fn) {
|
||||
this.name = name;
|
||||
this.fn = fn;
|
||||
}
|
||||
}
|
||||
|
||||
const _ctrls = {}
|
||||
|
||||
class CtrlWrapper {
|
||||
constructor(name, model, inject) {
|
||||
this.name = name;
|
||||
this.model = model;
|
||||
this.inject = inject;
|
||||
this.register = (view) => {
|
||||
this.inject.forEach((el) => {
|
||||
view.addEventListener(el.name, el.fn);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class HCtrl {
|
||||
static add(name, model, inject) {
|
||||
const w = new CtrlWrapper(name, model, inject);
|
||||
_ctrls[name] = w;
|
||||
}
|
||||
|
||||
static register(name, view) {
|
||||
_ctrls[name].register(view);
|
||||
}
|
||||
|
||||
static view(name) {
|
||||
if(!(name in _ctrls)) {
|
||||
throw Error(`View named : "${name}" not found.`)
|
||||
}
|
||||
return _ctrls[name].view;
|
||||
}
|
||||
static model(name) {
|
||||
if(!(name in _ctrls)) {
|
||||
throw Error(`View named : "${name}" not found.`)
|
||||
}
|
||||
return _ctrls[name].model
|
||||
}
|
||||
}
|
||||
|
||||
// i18n
|
||||
|
||||
const _i18comp = {};
|
||||
const _i18langs = {};
|
||||
let _currentLanguage = null;
|
||||
let _currentLanguage = 'en';
|
||||
|
||||
export class I18 {
|
||||
|
||||
@ -209,40 +251,97 @@ export class I18 {
|
||||
throw Error(`Language not found ${lang}`);
|
||||
}
|
||||
_currentLanguage = lang;
|
||||
const data = _i18langs[lang].data;
|
||||
const ldata = _i18langs[lang];
|
||||
if(ldata.data) {
|
||||
I18.translate(ldata);
|
||||
} else if (ldata.path) {
|
||||
I18.getData(lang, ldata.path, I18.translate);
|
||||
} else {
|
||||
throw Error(`Invalid language definition for language ${lang}`);
|
||||
}
|
||||
}
|
||||
|
||||
static loadLanguage(lang) {
|
||||
const ldata = _i18langs[lang];
|
||||
return I18.getData(lang, ldata.path);
|
||||
}
|
||||
|
||||
static getData(lang, path, handler) {
|
||||
return HTTP({
|
||||
url: path
|
||||
}).then((resp) => {
|
||||
const data = JSON.parse(resp.data);
|
||||
_i18langs[lang].data = data.data;
|
||||
if(handler) handler(data);
|
||||
});
|
||||
}
|
||||
|
||||
static translate(lang) {
|
||||
const data = lang.data;
|
||||
for(let key in _i18comp) {
|
||||
if(!(key in data)) {
|
||||
console.warn(`Missing translation for language "${lang}" key: "${key}" setting current value`);
|
||||
_i18langs[lang].data[key] = _i18comp[key].innerText;
|
||||
} else {
|
||||
I18.t(_i18comp[key], data[key]);
|
||||
|
||||
}
|
||||
I18.t(_i18comp[key], data[key]);
|
||||
}
|
||||
}
|
||||
|
||||
static register(key, el) {
|
||||
if(key in _i18comp) {
|
||||
throw Error(`Duplicate key ${key}`);
|
||||
if(!(key in _i18comp)) {
|
||||
_i18comp[key] = [];
|
||||
}
|
||||
_i18comp[key] = el;
|
||||
_i18comp[key].push(el);
|
||||
}
|
||||
|
||||
static t(el, data) {
|
||||
if(typeof data == "string" || typeof data == "number") {
|
||||
el.innerText = data;
|
||||
static t(elarr, data) {
|
||||
if(!data) return;
|
||||
elarr.forEach((el) => {
|
||||
if(typeof data == "string" || typeof data == "number") {
|
||||
el.innerText = data;
|
||||
} else {
|
||||
let txt = data.txt;
|
||||
data.var.forEach((el) => {
|
||||
const model = _ctrls[el.model].model
|
||||
txt = txt.replace(`{{${el.key}}}`, model.get(el.key))
|
||||
});
|
||||
el.innerText = txt;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static getKey(key, model) {
|
||||
const ldata = _i18langs[_currentLanguage];
|
||||
if (ldata.data) {
|
||||
const t = _i18langs[_currentLanguage].data[key];
|
||||
return I18.tkey(t, model);
|
||||
} else {
|
||||
throw Error(`TODO implement dynamic data type ${data}`);
|
||||
throw Error(`Language not loaded ${_currentLanguage}`);
|
||||
}
|
||||
}
|
||||
|
||||
static getKey(key) {
|
||||
return _i18langs[_currentLanguage].data[key];
|
||||
}
|
||||
|
||||
static getLKey(lang, key) {
|
||||
static getLKey(lang, key, model) {
|
||||
if(!(lang in _i18langs)) {
|
||||
throw Error(`Language not found ${lang}`);
|
||||
}
|
||||
return _i18langs[lang].data[key];
|
||||
const ldata = _i18langs[lang];
|
||||
if (ldata.data) {
|
||||
const t = _i18langs[lang].data[key];
|
||||
return I18.tkey(t, model);
|
||||
} else {
|
||||
throw Error(`Language not loaded ${lang}`);
|
||||
}
|
||||
}
|
||||
|
||||
static tkey(t, model) {
|
||||
if(typeof t == "string" || typeof t == "number") {
|
||||
return t;
|
||||
} else {
|
||||
let txt = t.txt;
|
||||
t.var.forEach((el) => {
|
||||
txt = txt.replace(`{{${el.key}}}`, model.get(el.key))
|
||||
});
|
||||
return txt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@szczepano/huh",
|
||||
"version": "0.0.4",
|
||||
"version": "0.0.5",
|
||||
"description": "huh",
|
||||
"main": "huh.js",
|
||||
"license": "BSD-3-Clause",
|
||||
|
Loading…
Reference in New Issue
Block a user