diff --git a/package.json b/package.json index 63063fbe..6318c9cd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/Registries.ts b/src/Registries.ts index 10a4a9b2..32c27848 100644 --- a/src/Registries.ts +++ b/src/Registries.ts @@ -1,4 +1,5 @@ import { INode } from "./nodes/AbstractNode" +import { Path, PathElement } from "./model/Path" export interface Registry { register(id: string, value: T): void @@ -6,14 +7,14 @@ export interface Registry { } class SchemaRegistry implements Registry> { - private registery: { [id: string]: INode } = {} + private registry: { [id: string]: INode } = {} register(id: string, node: INode) { - 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> { } class CollectionRegistry implements Registry { - 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 { } } +export interface Locale { + [key: string]: string +} + +class LocaleRegistry implements Registry { + 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() +} diff --git a/src/app/app.ts b/src/app/app.ts index a4948eb7..47dfb7bc 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -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() + }) diff --git a/src/locales/en.json b/src/locales/en.json new file mode 100644 index 00000000..96ffa999 --- /dev/null +++ b/src/locales/en.json @@ -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" +} diff --git a/src/minecraft/nodes/RangeNode.ts b/src/minecraft/nodes/RangeNode.ts index af077c48..f6aad652 100644 --- a/src/minecraft/nodes/RangeNode.ts +++ b/src/minecraft/nodes/RangeNode.ts @@ -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 implements StateNode input = `` } else if (value.type === 'binomial') { curType = 'binomial' - input = ` + input = ` - + ` } else { curType = 'range' - input = ` + input = ` - + ` } const id = view.register(el => { @@ -78,11 +79,11 @@ export class RangeNode extends AbstractNode implements StateNode evt.stopPropagation() }) }) - return `${options?.hideLabel ? `` : ``} + return `${options?.hideLabel ? `` : ``} ${input}` } diff --git a/src/minecraft/nodes/ResourceNode.ts b/src/minecraft/nodes/ResourceNode.ts index cab5a314..609b64b5 100644 --- a/src/minecraft/nodes/ResourceNode.ts +++ b/src/minecraft/nodes/ResourceNode.ts @@ -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 { 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 ? `` : ``} + return `${options?.hideLabel ? `` : ``} - ${this.options.map(o => `` + ${this.options.map(o => + `` + ).join('')}` } else { return super.renderRaw(path, value, view, options) } diff --git a/src/model/Path.ts b/src/model/Path.ts index 35dbaa23..e192131a 100644 --- a/src/model/Path.ts +++ b/src/model/Path.ts @@ -1,6 +1,6 @@ import { DataModel } from "./DataModel" -type PathElement = (string | number) +export type PathElement = (string | number) export class Path implements Iterable { private arr: PathElement[] @@ -27,6 +27,10 @@ export class Path implements Iterable { return new Path([...this.arr], this.model) } + getArray(): PathElement[] { + return this.arr + } + withModel(model: DataModel): Path { return new Path([...this.arr], model) } diff --git a/src/nodes/BooleanNode.ts b/src/nodes/BooleanNode.ts index 3ff30c66..5c24de4a 100644 --- a/src/nodes/BooleanNode.ts +++ b/src/nodes/BooleanNode.ts @@ -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 { @@ -17,9 +18,11 @@ export class BooleanNode extends AbstractNode { const trueButton = view.registerClick(el => { view.model.set(path, !this.force() && value === true ? undefined : true) }) - return `${options?.hideLabel ? `` : ``} - False - True` + return `${options?.hideLabel ? `` : ``} + ${locale('false')} + ${locale('true')}` } getClassName() { diff --git a/src/nodes/EnumNode.ts b/src/nodes/EnumNode.ts index 86d2e0f6..712a38de 100644 --- a/src/nodes/EnumNode.ts +++ b/src/nodes/EnumNode.ts @@ -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 implements StateNode { protected options: string[] @@ -25,9 +26,11 @@ export class EnumNode extends AbstractNode implements StateNode renderRaw(path: Path, value: string, view: TreeView, options?: RenderOptions) { const id = view.register(el => (el as HTMLInputElement).value = value) - return `${options?.hideLabel ? `` : ``} + return `${options?.hideLabel ? `` : ``} ` } diff --git a/src/nodes/ListNode.ts b/src/nodes/ListNode.ts index 2cfe7690..9c7cd237 100644 --- a/src/nodes/ListNode.ts +++ b/src/nodes/ListNode.ts @@ -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 { protected children: INode @@ -31,8 +32,8 @@ export class ListNode extends AbstractNode { const button = view.registerClick(el => { view.model.set(path, [...value, this.children.default()]) }) - return ` - + return ` +
${value.map((obj, index) => { return this.renderEntry(path.push(index), obj, view) @@ -44,7 +45,7 @@ export class ListNode extends AbstractNode { const button = view.registerClick(el => { view.model.set(path, undefined) }) - return `
+ return `
${this.children.render(path, value, view, {hideLabel: true})}
` } diff --git a/src/nodes/MapNode.ts b/src/nodes/MapNode.ts index 78219abd..67d1bcd4 100644 --- a/src/nodes/MapNode.ts +++ b/src/nodes/MapNode.ts @@ -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 { const key = this.keys.getState(el.parentElement!) view.model.set(path.push(key), this.values.default()) }) - return ` + return ` ${this.keys.renderRaw(path, '', view, {hideLabel: true, syncModel: false})} - +
${Object.keys(value).map(key => { return this.renderEntry(path.push(key), value[key], view) @@ -48,7 +49,7 @@ export class MapNode extends AbstractNode { const button = view.registerClick(el => { view.model.set(path, undefined) }) - return `
+ return `
${this.values.render(path, value, view)}
` } diff --git a/src/nodes/NumberNode.ts b/src/nodes/NumberNode.ts index 18565a16..14f4c906 100644 --- a/src/nodes/NumberNode.ts +++ b/src/nodes/NumberNode.ts @@ -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 { integer?: boolean @@ -36,7 +37,7 @@ export class NumberNode extends AbstractNode implements StateNode${path.last()}`} + return `${options?.hideLabel ? `` : ``} ` } diff --git a/src/nodes/ObjectNode.ts b/src/nodes/ObjectNode.ts index e2952177..71f7b0e8 100644 --- a/src/nodes/ObjectNode.ts +++ b/src/nodes/ObjectNode.ts @@ -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 { } 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 `` + return `` } else { const id = view.registerClick(() => view.model.set(path, undefined)) - return ` + return `
- ${this.renderFields(path, value, view, activeFields)} + ${this.renderFields(path, value, view)}
` } } else { - value = value ?? {} - return ` + return `
- ${this.renderFields(path, value, view, activeFields)} + ${this.renderFields(path, value, view)}
` } } - 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('') diff --git a/src/nodes/StringNode.ts b/src/nodes/StringNode.ts index 52e3c762..2575d7d9 100644 --- a/src/nodes/StringNode.ts +++ b/src/nodes/StringNode.ts @@ -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 { allowEmpty?: boolean @@ -25,7 +26,7 @@ export class StringNode extends AbstractNode implements StateNode${path.last()}`} + return `${options?.hideLabel ? `` : ``} ` } diff --git a/webpack.config.js b/webpack.config.js index cac1473b..d4509cae 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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'} + ] + }) + ] }