mirror of
https://github.com/misode/misode.github.io.git
synced 2026-05-04 14:42:53 +00:00
Add locale support
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"author": "Misode",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"copy-webpack-plugin": "^6.0.1",
|
||||
"ts-loader": "^7.0.4",
|
||||
"typescript": "^3.9.3",
|
||||
"webpack": "^4.43.0",
|
||||
|
||||
+46
-6
@@ -1,4 +1,5 @@
|
||||
import { INode } from "./nodes/AbstractNode"
|
||||
import { Path, PathElement } from "./model/Path"
|
||||
|
||||
export interface Registry<T> {
|
||||
register(id: string, value: T): void
|
||||
@@ -6,14 +7,14 @@ export interface Registry<T> {
|
||||
}
|
||||
|
||||
class SchemaRegistry implements Registry<INode<any>> {
|
||||
private registery: { [id: string]: INode<any> } = {}
|
||||
private registry: { [id: string]: INode<any> } = {}
|
||||
|
||||
register(id: string, node: INode<any>) {
|
||||
this.registery[id] = node
|
||||
this.registry[id] = node
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
const node = this.registery[id]
|
||||
const node = this.registry[id]
|
||||
if (node === undefined) {
|
||||
console.error(`Tried to access schema "${id}, but that doesn't exit.`)
|
||||
}
|
||||
@@ -22,14 +23,14 @@ class SchemaRegistry implements Registry<INode<any>> {
|
||||
}
|
||||
|
||||
class CollectionRegistry implements Registry<string[]> {
|
||||
private registery: { [id: string]: string[] } = {}
|
||||
private registry: { [id: string]: string[] } = {}
|
||||
|
||||
register(id: string, list: string[]) {
|
||||
this.registery[id] = list
|
||||
this.registry[id] = list
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
const list = this.registery[id]
|
||||
const list = this.registry[id]
|
||||
if (list === undefined) {
|
||||
console.warn(`Tried to access collection "${id}", but that doesn't exist`)
|
||||
}
|
||||
@@ -37,5 +38,44 @@ class CollectionRegistry implements Registry<string[]> {
|
||||
}
|
||||
}
|
||||
|
||||
export interface Locale {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
class LocaleRegistry implements Registry<Locale> {
|
||||
private registry: { [id: string]: Locale } = {}
|
||||
language: string = ''
|
||||
|
||||
register(id: string, locale: Locale): void {
|
||||
this.registry[id] = locale
|
||||
}
|
||||
|
||||
get(id: string) {
|
||||
const locale = this.registry[id]
|
||||
if (locale === undefined) {
|
||||
console.warn(`Tried to access locale "${id}", but that doesn't exist`)
|
||||
}
|
||||
return locale ?? []
|
||||
}
|
||||
|
||||
getLocale(key: string) {
|
||||
return this.get(this.language)[key]
|
||||
}
|
||||
}
|
||||
|
||||
export const SCHEMAS = new SchemaRegistry()
|
||||
export const COLLECTIONS = new CollectionRegistry()
|
||||
export const LOCALES = new LocaleRegistry()
|
||||
|
||||
export const locale = (key: string | Path) => {
|
||||
if (typeof key === 'string') {
|
||||
return LOCALES.getLocale(key) ?? key
|
||||
}
|
||||
let path = key.getArray().filter(e => (typeof e === 'string'))
|
||||
while (path.length > 0) {
|
||||
const locale = LOCALES.getLocale(path.join('.'))
|
||||
if (locale !== undefined) return locale
|
||||
path.shift()
|
||||
}
|
||||
return key.last()
|
||||
}
|
||||
|
||||
+9
-1
@@ -3,6 +3,7 @@ import { TreeView } from '../view/TreeView'
|
||||
import { SourceView } from '../view/SourceView'
|
||||
import { ConditionSchema } from '../minecraft/schemas/Condition'
|
||||
import { SandboxSchema } from './Sandbox'
|
||||
import { LOCALES, locale } from '../Registries'
|
||||
|
||||
const predicateModel = new DataModel(ConditionSchema)
|
||||
const sandboxModel = new DataModel(SandboxSchema)
|
||||
@@ -29,4 +30,11 @@ document.getElementById('header')?.append(modelSelector)
|
||||
new TreeView(model, document!.getElementById('view')!)
|
||||
new SourceView(model, document!.getElementById('source')!)
|
||||
|
||||
model.invalidate()
|
||||
fetch('../build/locales/en.json')
|
||||
.then(r => r.json())
|
||||
.then(l => {
|
||||
LOCALES.register('en', l)
|
||||
LOCALES.language = 'en'
|
||||
|
||||
model.invalidate()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"add": "Add",
|
||||
"chance": "Chance",
|
||||
"condition": "Condition",
|
||||
"condition.random_chance": "Random Chance",
|
||||
"false": "False",
|
||||
"remove": "Remove",
|
||||
"true": "True",
|
||||
"type": "Type",
|
||||
"overworld": "Overworld",
|
||||
"the_nether": "The Nether",
|
||||
"the_end": "The End"
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { AbstractNode, NodeMods, RenderOptions, StateNode } from '../../nodes/Ab
|
||||
import { Path } from '../../model/Path'
|
||||
import { DataModel } from '../../model/DataModel'
|
||||
import { TreeView } from '../../view/TreeView'
|
||||
import { locale } from '../../Registries'
|
||||
|
||||
export type IRange = number
|
||||
| { min?: number, max?: number, type?: 'uniform' }
|
||||
@@ -57,15 +58,15 @@ export class RangeNode extends AbstractNode<IRange> implements StateNode<IRange>
|
||||
input = `<input value=${value === undefined ? '' : value}>`
|
||||
} else if (value.type === 'binomial') {
|
||||
curType = 'binomial'
|
||||
input = `<label>n</label>
|
||||
input = `<label>${locale('range.n')}</label>
|
||||
<input value=${value.n === undefined ? '' : value.n}>
|
||||
<label>p</label>
|
||||
<label>${locale('range.p')}</label>
|
||||
<input value=${value.p === undefined ? '' : value.p}>`
|
||||
} else {
|
||||
curType = 'range'
|
||||
input = `<label>Min</label>
|
||||
input = `<label>${locale('range.min')}</label>
|
||||
<input value=${value.min === undefined ? '' : value.min}>
|
||||
<label>Max</label>
|
||||
<label>${locale('range.max')}</label>
|
||||
<input value=${value.max === undefined ? '' : value.max}>`
|
||||
}
|
||||
const id = view.register(el => {
|
||||
@@ -78,11 +79,11 @@ export class RangeNode extends AbstractNode<IRange> implements StateNode<IRange>
|
||||
evt.stopPropagation()
|
||||
})
|
||||
})
|
||||
return `${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
|
||||
return `${options?.hideLabel ? `` : `<label>${locale(path)}</label>`}
|
||||
<select data-id="${id}">
|
||||
<option value="exact">Exact</option>
|
||||
<option value="range">Range</option>
|
||||
<option value="binomial">Binomial</option>
|
||||
<option value="exact">${locale('range.exact')}</option>
|
||||
<option value="range">${locale('range.range')}</option>
|
||||
<option value="binomial">${locale('range.binomial')}</option>
|
||||
</select>
|
||||
${input}`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { NodeMods, RenderOptions } from '../../nodes/AbstractNode'
|
||||
import { EnumNode } from '../../nodes/EnumNode'
|
||||
import { Path } from '../../model/Path'
|
||||
import { TreeView, getId } from '../../view/TreeView'
|
||||
import { locale } from '../../Registries'
|
||||
|
||||
export interface ResourceNodeMods extends NodeMods<string> {
|
||||
additional?: boolean
|
||||
@@ -30,9 +31,11 @@ export class ResourceNode extends EnumNode {
|
||||
renderRaw(path: Path, value: string, view: TreeView, options?: RenderOptions) {
|
||||
if (this.additional) {
|
||||
const id = `datalist-${getId()}`
|
||||
return `${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
|
||||
return `${options?.hideLabel ? `` : `<label>${locale(path)}</label>`}
|
||||
<input list=${id} value="${value ?? ''}">
|
||||
<datalist id=${id}>${this.options.map(o => `<option value="${o}">`).join('')}</datalist>`
|
||||
<datalist id=${id}>${this.options.map(o =>
|
||||
`<option value="${o}">${locale(path.push(o))}</option>`
|
||||
).join('')}</datalist>`
|
||||
} else {
|
||||
return super.renderRaw(path, value, view, options)
|
||||
}
|
||||
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
import { DataModel } from "./DataModel"
|
||||
|
||||
type PathElement = (string | number)
|
||||
export type PathElement = (string | number)
|
||||
|
||||
export class Path implements Iterable<PathElement> {
|
||||
private arr: PathElement[]
|
||||
@@ -27,6 +27,10 @@ export class Path implements Iterable<PathElement> {
|
||||
return new Path([...this.arr], this.model)
|
||||
}
|
||||
|
||||
getArray(): PathElement[] {
|
||||
return this.arr
|
||||
}
|
||||
|
||||
withModel(model: DataModel): Path {
|
||||
return new Path([...this.arr], model)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AbstractNode, NodeMods, RenderOptions } from "./AbstractNode";
|
||||
import { Path } from "../model/Path";
|
||||
import { TreeView } from "../view/TreeView";
|
||||
import { locale } from "../Registries";
|
||||
|
||||
export class BooleanNode extends AbstractNode<boolean> {
|
||||
|
||||
@@ -17,9 +18,11 @@ export class BooleanNode extends AbstractNode<boolean> {
|
||||
const trueButton = view.registerClick(el => {
|
||||
view.model.set(path, !this.force() && value === true ? undefined : true)
|
||||
})
|
||||
return `${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
|
||||
<button${value === false ? ' style="font-weight: bold"' : ' '} data-id="${falseButton}">False</button>
|
||||
<button${value === true ? ' style="font-weight: bold"' : ' '} data-id="${trueButton}">True</button>`
|
||||
return `${options?.hideLabel ? `` : `<label>${locale(path)}</label>`}
|
||||
<button${value === false ? ' style="font-weight: bold"' : ' '}
|
||||
data-id="${falseButton}">${locale('false')}</button>
|
||||
<button${value === true ? ' style="font-weight: bold"' : ' '}
|
||||
data-id="${trueButton}">${locale('true')}</button>`
|
||||
}
|
||||
|
||||
getClassName() {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AbstractNode, NodeMods, RenderOptions, StateNode } from './AbstractNode
|
||||
import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { Path } from '../model/Path'
|
||||
import { locale } from '../Registries'
|
||||
|
||||
export class EnumNode extends AbstractNode<string> implements StateNode<string> {
|
||||
protected options: string[]
|
||||
@@ -25,9 +26,11 @@ export class EnumNode extends AbstractNode<string> implements StateNode<string>
|
||||
|
||||
renderRaw(path: Path, value: string, view: TreeView, options?: RenderOptions) {
|
||||
const id = view.register(el => (el as HTMLInputElement).value = value)
|
||||
return `${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
|
||||
return `${options?.hideLabel ? `` : `<label>${locale(path)}</label>`}
|
||||
<select data-id=${id}>
|
||||
${this.options.map(o => `<option value="${o}">${o}</option>`).join('')}
|
||||
${this.options.map(o =>
|
||||
`<option value="${o}">${locale(path.push(o))}</option>`
|
||||
).join('')}
|
||||
</select>`
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { Path } from '../model/Path'
|
||||
import { IObject } from './ObjectNode'
|
||||
import { locale } from '../Registries'
|
||||
|
||||
export class ListNode extends AbstractNode<IObject[]> {
|
||||
protected children: INode<any>
|
||||
@@ -31,8 +32,8 @@ export class ListNode extends AbstractNode<IObject[]> {
|
||||
const button = view.registerClick(el => {
|
||||
view.model.set(path, [...value, this.children.default()])
|
||||
})
|
||||
return `<label>${path.last()}:</label>
|
||||
<button data-id="${button}">Add</button>
|
||||
return `<label>${locale(path)}:</label>
|
||||
<button data-id="${button}">${locale('add')}</button>
|
||||
<div class="list-fields">
|
||||
${value.map((obj, index) => {
|
||||
return this.renderEntry(path.push(index), obj, view)
|
||||
@@ -44,7 +45,7 @@ export class ListNode extends AbstractNode<IObject[]> {
|
||||
const button = view.registerClick(el => {
|
||||
view.model.set(path, undefined)
|
||||
})
|
||||
return `<div class="list-entry"><button data-id="${button}">Remove</button>
|
||||
return `<div class="list-entry"><button data-id="${button}">${locale('remove')}</button>
|
||||
${this.children.render(path, value, view, {hideLabel: true})}
|
||||
</div>`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AbstractNode, NodeMods, INode, StateNode } from './AbstractNode'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { Path } from '../model/Path'
|
||||
import { IObject } from './ObjectNode'
|
||||
import { locale } from '../Registries'
|
||||
|
||||
export type IMap = {
|
||||
[name: string]: IObject
|
||||
@@ -34,9 +35,9 @@ export class MapNode extends AbstractNode<IMap> {
|
||||
const key = this.keys.getState(el.parentElement!)
|
||||
view.model.set(path.push(key), this.values.default())
|
||||
})
|
||||
return `<label>${path.last()}:</label>
|
||||
return `<label>${locale(path)}:</label>
|
||||
${this.keys.renderRaw(path, '', view, {hideLabel: true, syncModel: false})}
|
||||
<button data-id="${button}">Add</button>
|
||||
<button data-id="${button}">${locale('add')}</button>
|
||||
<div class="map-fields">
|
||||
${Object.keys(value).map(key => {
|
||||
return this.renderEntry(path.push(key), value[key], view)
|
||||
@@ -48,7 +49,7 @@ export class MapNode extends AbstractNode<IMap> {
|
||||
const button = view.registerClick(el => {
|
||||
view.model.set(path, undefined)
|
||||
})
|
||||
return `<div class="map-entry"><button data-id="${button}">Remove</button>
|
||||
return `<div class="map-entry"><button data-id="${button}">${locale('remove')}</button>
|
||||
${this.values.render(path, value, view)}
|
||||
</div>`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AbstractNode, NodeMods, RenderOptions, StateNode } from './AbstractNode
|
||||
import { Path } from '../model/Path'
|
||||
import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { locale } from '../Registries'
|
||||
|
||||
export interface NumberNodeMods extends NodeMods<number> {
|
||||
integer?: boolean
|
||||
@@ -36,7 +37,7 @@ export class NumberNode extends AbstractNode<number> implements StateNode<number
|
||||
}
|
||||
|
||||
renderRaw(path: Path, value: number, view: TreeView, options?: RenderOptions) {
|
||||
return `${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
|
||||
return `${options?.hideLabel ? `` : `<label>${locale(path)}</label>`}
|
||||
<input value="${value ?? ''}">`
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -1,6 +1,7 @@
|
||||
import { NodeMods, INode, NodeChildren, AbstractNode, RenderOptions } from './AbstractNode'
|
||||
import { Path } from '../model/Path'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { locale } from '../Registries'
|
||||
|
||||
export const Switch = Symbol('switch')
|
||||
export const Case = Symbol('case')
|
||||
@@ -52,32 +53,31 @@ export class ObjectNode extends AbstractNode<IObject> {
|
||||
}
|
||||
|
||||
renderRaw(path: Path, value: IObject, view: TreeView, options?: RenderOptions) {
|
||||
const activeCase = this.filter ? this.cases[value[this.filter]] : {};
|
||||
const activeFields = {...this.fields, ...activeCase}
|
||||
if (options?.hideLabel) {
|
||||
value = value ?? {}
|
||||
return this.renderFields(path, value, view, activeFields)
|
||||
return this.renderFields(path, value, view)
|
||||
} else if (this.collapse || options?.collapse) {
|
||||
if (value === undefined) {
|
||||
const id = view.registerClick(() => view.model.set(path, this.default()))
|
||||
return `<label class="collapse closed" data-id="${id}">${path.last()}</label>`
|
||||
return `<label class="collapse closed" data-id="${id}">${locale(path)}</label>`
|
||||
} else {
|
||||
const id = view.registerClick(() => view.model.set(path, undefined))
|
||||
return `<label class="collapse open" data-id="${id}">${path.last()}</label>
|
||||
return `<label class="collapse open" data-id="${id}">${locale(path)}</label>
|
||||
<div class="object-fields">
|
||||
${this.renderFields(path, value, view, activeFields)}
|
||||
${this.renderFields(path, value, view)}
|
||||
</div>`
|
||||
}
|
||||
} else {
|
||||
value = value ?? {}
|
||||
return `<label>${path.last()}</label>
|
||||
return `<label>${locale(path)}</label>
|
||||
<div class="object-fields">
|
||||
${this.renderFields(path, value, view, activeFields)}
|
||||
${this.renderFields(path, value, view)}
|
||||
</div>`
|
||||
}
|
||||
}
|
||||
|
||||
renderFields(path: Path, value: IObject, view: TreeView, activeFields: NodeChildren) {
|
||||
renderFields(path: Path, value: IObject, view: TreeView) {
|
||||
value = value ?? {}
|
||||
const activeCase = this.filter ? this.cases[value[this.filter]] : {};
|
||||
const activeFields = {...this.fields, ...activeCase}
|
||||
return Object.keys(activeFields).map(f => {
|
||||
return activeFields[f].render(path.push(f), value[f], view)
|
||||
}).join('')
|
||||
|
||||
@@ -2,6 +2,7 @@ import { AbstractNode, NodeMods, RenderOptions, StateNode } from './AbstractNode
|
||||
import { Path } from '../model/Path'
|
||||
import { DataModel } from '../model/DataModel'
|
||||
import { TreeView } from '../view/TreeView'
|
||||
import { locale } from '../Registries'
|
||||
|
||||
export interface StringNodeMods extends NodeMods<string> {
|
||||
allowEmpty?: boolean
|
||||
@@ -25,7 +26,7 @@ export class StringNode extends AbstractNode<string> implements StateNode<string
|
||||
}
|
||||
|
||||
renderRaw(path: Path, value: string, view: TreeView, options?: RenderOptions) {
|
||||
return `${options?.hideLabel ? `` : `<label>${path.last()}</label>`}
|
||||
return `${options?.hideLabel ? `` : `<label>${locale(path)}</label>`}
|
||||
<input value="${value ?? ''}">`
|
||||
}
|
||||
|
||||
|
||||
+10
-1
@@ -1,3 +1,5 @@
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
entry: './src/app/app.ts',
|
||||
output: {
|
||||
@@ -11,5 +13,12 @@ module.exports = {
|
||||
rules: [
|
||||
{ test: /\.ts$/, loader: 'ts-loader' }
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: 'src/locales', to: 'build/locales'}
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user