diff --git a/package-lock.json b/package-lock.json index 7830a113..9fdaf09b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,16 +5,16 @@ "requires": true, "dependencies": { "@mcschema/core": { - "version": "0.10.0-beta.6", - "resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.10.0-beta.6.tgz", - "integrity": "sha512-52QcHOBuATSL9dqI3fuNMexuJFwwF9NrtILs+IzkwAt/40e7VnR29doJW8P3JkNcZ2a9BaVtw5ExfbePj9rTFg==" + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@mcschema/core/-/core-0.11.0.tgz", + "integrity": "sha512-6FZxNI2PQleT/4wDIatL7PvTZRHvbD5zWzcJLl0JgvKu7G6XXmox+TNT3L7j/auGCDSSf4wq+n+MtoXYxZ/EnQ==" }, "@mcschema/java-1.16": { - "version": "0.5.9-beta.1", - "resolved": "https://registry.npmjs.org/@mcschema/java-1.16/-/java-1.16-0.5.9-beta.1.tgz", - "integrity": "sha512-Yh+Z6jH/GU/WgshsHfz6aRZLXGMldhaHgWfk+eWvY3f8ftrurNW+OUddCx3kwOZ4gADy5pivVhISB1yecpWKxQ==", + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@mcschema/java-1.16/-/java-1.16-0.5.12.tgz", + "integrity": "sha512-o6bnFQgilAYYMLklv3buBbVRDeK3THN2FiHHaJqHiFbs+EeNpcJ0tWm/PDRgrTUnw8TIfiNft6Ro992NwWXkkg==", "requires": { - "@mcschema/core": "^0.10.0-beta.3" + "@mcschema/core": "^0.11.0" } }, "@mcschema/locales": { diff --git a/package.json b/package.json index f3803920..93d1e6f8 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "author": "Misode", "license": "MIT", "dependencies": { - "@mcschema/core": "^0.10.0-beta.6", - "@mcschema/java-1.16": "^0.5.9-beta.1", + "@mcschema/core": "^0.11.0", + "@mcschema/java-1.16": "^0.5.12", "@mcschema/locales": "^0.1.11", "@types/google.analytics": "0.0.40", "@types/split.js": "^1.4.0", diff --git a/src/app/ErrorsView.ts b/src/app/ErrorsView.ts index 9664d230..785b91b2 100644 --- a/src/app/ErrorsView.ts +++ b/src/app/ErrorsView.ts @@ -1,9 +1,9 @@ import { DataModel, Errors, - locale, } from '@mcschema/core' import { AbstractView } from './AbstractView' +import { locale } from './locales' export class ErrorsView extends AbstractView { target: HTMLElement diff --git a/src/app/app.ts b/src/app/app.ts index 1e4965dc..40deb0cf 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -1,12 +1,5 @@ import Split from 'split.js' -import { - Base, - DataModel, - locale, - LOCALES, - ModelPath, - Path, -} from '@mcschema/core' +import { Base, DataModel, ModelPath, Path } from '@mcschema/core' import { getCollections, getSchemas } from '@mcschema/java-1.16' import { VisualizerView } from './visualization/VisualizerView' import { RegistryFetcher } from './RegistryFetcher' @@ -16,6 +9,7 @@ import { ErrorsView } from './ErrorsView' import config from '../config.json' import { BiomeNoiseVisualizer } from './visualization/BiomeNoiseVisualizer' import { Mounter } from './Mounter' +import { getLanguage, hasLocale, locale, registerLocale, setLanguage } from './locales' const LOCAL_STORAGE_THEME = 'theme' const LOCAL_STORAGE_LANGUAGE = 'language' @@ -92,9 +86,9 @@ const treeViewNodeInjector = (path: ModelPath, mounter: Mounter) => { const fetchLocale = async (id: string) => { const response = await fetch(publicPath + `locales/${id}.json`) - LOCALES.register(id, await response.json()) + registerLocale(id, await response.json()) } -LOCALES.language = localStorage.getItem(LOCAL_STORAGE_LANGUAGE)?.toLowerCase() ?? 'en' +setLanguage(localStorage.getItem(LOCAL_STORAGE_LANGUAGE)?.toLowerCase()) const homeLink = document.getElementById('home-link')! const homeGenerators = document.getElementById('home-generators')! @@ -151,8 +145,8 @@ const views = { const COLLECTIONS = getCollections() Promise.all([ - fetchLocale(LOCALES.language), - ...(LOCALES.language === 'en' ? [] : [fetchLocale('en')]), + fetchLocale(getLanguage()), + ...(getLanguage() === 'en' ? [] : [fetchLocale('en')]), RegistryFetcher(COLLECTIONS, config.registries) ]).then(responses => { @@ -182,29 +176,29 @@ Promise.all([ } selectedModel.textContent = title document.title = title - } - - const updateLanguage = (id: string, store = false) => { - LOCALES.language = id - if (store) { - localStorage.setItem(LOCAL_STORAGE_LANGUAGE, id) - } document.querySelectorAll('[data-i18n]').forEach(el => { el.textContent = locale(el.attributes.getNamedItem('data-i18n')!.value) }) + } + + const updateLanguage = (id: string, store = false) => { + setLanguage(id) + if (store) { + localStorage.setItem(LOCAL_STORAGE_LANGUAGE, id) + } languageSelectorMenu.innerHTML = '' config.languages.forEach(lang => { languageSelectorMenu.insertAdjacentHTML('beforeend', - `
${lang.name}
`) + `
${lang.name}
`) languageSelectorMenu.lastChild?.addEventListener('click', evt => { updateLanguage(lang.code, true) languageSelectorMenu.style.visibility = 'hidden' }) }) - if (LOCALES.has(id)) { + if (hasLocale(id)) { updateModel() } else { fetchLocale(id).then(r => { @@ -392,7 +386,7 @@ Promise.all([ } } - updateLanguage(LOCALES.language) + updateLanguage(getLanguage()) } reload(location.pathname, false) document.body.style.visibility = 'initial' diff --git a/src/app/hooks/renderHtml.ts b/src/app/hooks/renderHtml.ts index 9a9f808a..6f99dc88 100644 --- a/src/app/hooks/renderHtml.ts +++ b/src/app/hooks/renderHtml.ts @@ -1,4 +1,5 @@ -import { locale, Hook, ModelPath, Path, StringHookParams, ValidationOption, EnumOption, INode, DataModel, MapNode, StringNode } from '@mcschema/core' +import { Hook, ModelPath, Path, StringHookParams, ValidationOption, EnumOption, INode, DataModel, MapNode, StringNode } from '@mcschema/core' +import { errorLocale, helpLocale, locale, pathLocale, segmentedLocale } from '../locales' import { Mounter } from '../Mounter' import { hexId } from '../utils' @@ -39,12 +40,12 @@ export const renderHtml: Hook<[any, Mounter], [string, string, string]> = { const pathWithChoiceContext = config?.choiceContext ? new Path([], [config.choiceContext]) : config?.context ? new Path([], [config.context]) : path const inject = choices.map(c => { if (c.type === choice.type) { - return `` + return `` } const buttonId = mounter.registerClick(el => { path.model.set(path, c.change ? c.change(value) : c.node.default()) }) - return `` + return `` }).join('') const [prefix, suffix, body] = choice.node.hook(this, pathWithContext, value, mounter) @@ -67,14 +68,14 @@ export const renderHtml: Hook<[any, Mounter], [string, string, string]> = { if (Array.isArray(value)) { body = value.map((childValue, index) => { const removeId = mounter.registerClick(el => path.model.set(path.push(index), undefined)) - const childPath = path.push(index).localePush('entry') + const childPath = path.push(index).contextPush('entry') const category = children.category(childPath) const [cPrefix, cSuffix, cBody] = children.hook(this, childPath, childValue, mounter) - return `
+ return `
${cPrefix} - + ${cSuffix}
${cBody ? `
${cBody}
` : ''} @@ -109,7 +110,7 @@ export const renderHtml: Hook<[any, Mounter], [string, string, string]> = { const childPath = path.modelPush(key) const category = children.category(childPath) const [cPrefix, cSuffix, cBody] = children.hook(this, childPath, value[key], mounter) - return `
+ return `
${cPrefix} @@ -159,10 +160,10 @@ export const renderHtml: Hook<[any, Mounter], [string, string, string]> = { const childPath = getChildModelPath(path, k) const category = field.category(childPath) const [cPrefix, cSuffix, cBody] = field.hook(this, childPath, value[k], mounter) - return `
+ return `
${cPrefix} - + ${cSuffix}
${cBody ? `
${cBody}
` : ''} @@ -207,7 +208,7 @@ function rawString({ node, getValues, config }: { node: INode } & StringHookPara && typeof config.params.pool === 'string' && values.length > 0) { const contextPath = new Path(path.getArray(), [config.params.pool]) - if (contextPath.localePush(values[0]).strictLocale()) { + if (segmentedLocale(contextPath.contextPush(values[0]).getContext())) { return selectRaw(node, contextPath, values, inputId) } } @@ -225,7 +226,7 @@ function selectRaw(node: INode, contextPath: Path, values: string[], inputId?: s return `` } diff --git a/src/app/locales.ts b/src/app/locales.ts new file mode 100644 index 00000000..17023fa6 --- /dev/null +++ b/src/app/locales.ts @@ -0,0 +1,72 @@ +import { ModelPath, Path } from '@mcschema/core' + +interface Locale { + [key: string]: string +} + +const Locales: { + [key: string]: Locale +} = {} + +let language = 'en' + +export function registerLocale(code: string, locale: Locale) { + Locales[code] = locale +} + +export function hasLocale(code: string) { + return Locales[code] !== undefined +} + +export function setLanguage(code: string | undefined) { + language = code ?? language +} + +export function getLanguage() { + return language +} + +export function resolveLocaleParams(value: string, params?: string[]): string | undefined { + return value?.replace(/%\d+%/g, match => { + const index = parseInt(match.slice(1, -1)) + return params?.[index] !== undefined ? params[index] : match + }) +} + +export function locale(key: string, params?: string[]): string { + const value: string | undefined = Locales[language][key] ?? Locales.en[key] + return resolveLocaleParams(value, params) ?? key +} + +export function segmentedLocale(segments: string[], params?: string[], depth = 5, minDepth = 1): string | undefined { + return [language, 'en'].reduce((prev: string | undefined, code) => { + if (prev !== undefined) return prev + + const array = segments.slice(-depth); + while (array.length >= minDepth) { + const locale = resolveLocaleParams(Locales[code][array.join('.')], params) + if (locale !== undefined) return locale + array.shift() + } + + return undefined + }, undefined) +} + +export function pathLocale(path: Path, params?: string[]): string { + // return path.getContext().slice(-5).join('.') + return segmentedLocale(path.getContext(), params) + ?? path.getContext()[path.getContext().length - 1] ?? '' +} + +export function errorLocale(p: ModelPath, exact = true): string { + const errors = p.model.errors.get(p, exact) + if (errors.length === 0) return '' + return `data-error="${locale(errors[0].error, errors[0].params)}"` +} + +export function helpLocale(path: ModelPath): string { + const res = segmentedLocale(path.contextPush('help').getContext(), [], 6) + if (res === undefined) return '' + return `data-help="${res}"` +}