Reimplemented localisation

This commit is contained in:
Lucas Dower 2023-04-09 20:10:59 +01:00
parent 9e3dac7e34
commit d96dddbcf6
No known key found for this signature in database
GPG Key ID: B3EE6B8499593605
24 changed files with 243 additions and 90 deletions

View File

@ -16,6 +16,7 @@
"simple-import-sort"
],
"rules": {
"camelcase": "off",
"linebreak-style": "off",
"object-curly-spacing": "off",
"max-len": "off",

11
loc/base.ts Normal file
View File

@ -0,0 +1,11 @@
import { en_GB } from './en_GB';
import { en_US } from './en_US';
export type TTranslationMap = typeof en_GB;
export const locales = {
en_GB: { translation: en_GB },
en_US: { translation: en_US },
};
export type TLocales = keyof typeof locales;

59
loc/en_GB.ts Normal file
View File

@ -0,0 +1,59 @@
// Credits:
// LucasDower
export const en_GB = {
something_went_wrong: 'Something unexpectedly went wrong',
init: {
initialising: 'Initialising...',
ready: 'Ready',
},
import: {
heading: 'Import',
importing_mesh: 'Importing mesh...',
imported_mesh: 'Imported mesh',
rendering_mesh: 'Rendering mesh...',
rendered_mesh: 'Rendered mesh',
no_vertices_loaded: 'No vertices were loaded',
no_triangles_loaded: 'No triangles were loaded',
could_not_scale_mesh: 'Could not scale mesh correctly - mesh is likely 2D, rotate it so that it has a non-zero height',
invalid_encoding: 'Unrecognised character found, please encode using UTF-8',
invalid_face_data: 'Face data has unexpected number of vertex data: {{count, number}}',
too_many_triangles: 'The imported mesh has {{count, number}} triangles, consider simplifying it in a DDC such as Blender',
vertex_triangle_count: '{{vertex_count, number}} vertices, {{triangle_count, number}} triangles',
missing_normals: 'Some vertices do not have their normals defined, this may cause voxels to be aligned incorrectly',
failed_to_parse_line: 'Failed attempt to parse "{{line}}", because "{{error}}"',
},
materials: {
updating_materials: 'Updating materials...',
updated_materials: 'Updated materials',
},
voxelise: {
loading_voxel_mesh: 'Loading voxel mesh...',
loaded_voxel_mesh: 'Loaded voxel mesh',
rendering_voxel_mesh: 'Rendering voxel mesh...',
rendered_voxel_mesh: 'Rendered voxel mesh',
voxel_count: '{{count, number}} voxels',
voxel_mesh_dimensions: 'Dimensions are {{x, number}} x {{y, number}} x {{z, number}}',
},
assign: {
loading_block_mesh: 'Loading block mesh...',
loaded_block_mesh: 'Loaded block mesh',
rendering_block_mesh: 'Rendering block mesh...',
rendered_block_mesh: 'Rendered block mesh',
deselected_blocks: 'Deselected {{count, number}} blocks',
selected_blocks: 'Selected {{count, number}} blocks',
found_blocks: 'Found {{count, number}} blocks',
block_not_namespaced: '"{{block_name}}" is not namespaced correctly, do you mean "minecraft:{{block_name}}"?',
could_not_use_block: 'Could not use "{{block_name}}" as it is unsupported',
reading_palette: 'Reading {{file_name}}...',
block_palette_missing_light_blocks: 'Block palette contains no light blocks to place',
blocks_missing_textures: '{{count, number}} palette block(s) are missing atlas textures, they will not be used',
falling_blocks: '{{count, number}} blocks will fall due to gravity when this structure is placed',
},
export: {
exporting_structure: 'Exporting structure...',
exported_structure: 'Exported structure',
schematic_unsupported_blocks: '{{count, number}} blocks ({{unique, number}} unique) are not supported by the .schematic format, Stone blocks will used instead. Try using the schematic-friendly palette, or export using .litematica',
nbt_exporter_too_big: 'Structure blocks only support structures of size 48x48x48, blocks outside this range will be removed',
},
};

7
loc/en_US.ts Normal file
View File

@ -0,0 +1,7 @@
// Credits:
// LucasDower
import { TTranslationMap } from './base';
export const en_US: Partial<TTranslationMap> = {
};

33
package-lock.json generated
View File

@ -35,6 +35,7 @@
"eslint-plugin-simple-import-sort": "^8.0.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"i18next": "^22.4.14",
"images": "^3.2.3",
"jest": "^27.5.1",
"jpeg-js": "^0.4.4",
@ -5411,6 +5412,29 @@
"node": ">=10.17.0"
}
},
"node_modules/i18next": {
"version": "22.4.14",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.14.tgz",
"integrity": "sha512-VtLPtbdwGn0+DAeE00YkiKKXadkwg+rBUV+0v8v0ikEjwdiJ0gmYChVE4GIa9HXymY6wKapkL93vGT7xpq6aTw==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"dependencies": {
"@babel/runtime": "^7.20.6"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -14886,6 +14910,15 @@
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true
},
"i18next": {
"version": "22.4.14",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.14.tgz",
"integrity": "sha512-VtLPtbdwGn0+DAeE00YkiKKXadkwg+rBUV+0v8v0ikEjwdiJ0gmYChVE4GIa9HXymY6wKapkL93vGT7xpq6aTw==",
"dev": true,
"requires": {
"@babel/runtime": "^7.20.6"
}
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",

View File

@ -49,6 +49,7 @@
"eslint-plugin-simple-import-sort": "^8.0.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0",
"i18next": "^22.4.14",
"images": "^3.2.3",
"jest": "^27.5.1",
"jpeg-js": "^0.4.4",

View File

@ -4,6 +4,7 @@ import { FallableBehaviour } from './block_mesh';
import { ArcballCamera } from './camera';
import { AppConfig } from './config';
import { EventManager } from './event';
import { LOC, Localiser, TLocalisedString } from './localiser';
import { MaterialMapManager } from './material-map';
import { MouseManager } from './mouse';
import { MeshType, Renderer } from './renderer';
@ -34,14 +35,17 @@ export class AppContext {
this._materialManager = new MaterialMapManager(new Map());
}
public static init() {
AppConsole.info('Initialising...');
public static async init() {
await Localiser.Get.init();
AppConsole.info(LOC('init.initialising'));
Logger.Get.enableLOG();
Logger.Get.enableLOGMAJOR();
Logger.Get.enableLOGWARN();
AppConfig.Get.dumpConfig();
EventManager.Get.bindToContext(this.Get);
UI.Get.bindToContext(this.Get);
@ -57,7 +61,7 @@ export class AppContext {
this.Get._workerController.execute({ action: 'Init', params: {}}).then(() => {
UI.Get.enable(EAction.Import);
AppConsole.success('Ready');
AppConsole.success(LOC('init.ready'));
});
ArcballCamera.Get.toggleAngleSnap();
@ -71,7 +75,7 @@ export class AppContext {
// Gather data from the UI to send to the worker
const components = UI.Get.layout.import.components;
AppConsole.info('Importing mesh...');
AppConsole.info(LOC('import.importing_mesh'));
{
// Instruct the worker to perform the job and await the result
const resultImport = await this._workerController.execute({
@ -88,7 +92,7 @@ export class AppContext {
}
ASSERT(resultImport.action === 'Import');
AppConsole.success('Imported mesh');
AppConsole.success(LOC('import.imported_mesh'));
this._addWorkerMessagesToConsole(resultImport.messages);
this.maxConstraint = Vector3.copy(resultImport.result.dimensions)
@ -97,7 +101,7 @@ export class AppContext {
UI.Get.updateMaterialsAction(this._materialManager);
}
AppConsole.info('Rendering mesh...');
AppConsole.info(LOC('import.rendering_mesh'));
{
// Instruct the worker to perform the job and await the result
const resultRender = await this._workerController.execute({
@ -114,14 +118,14 @@ export class AppContext {
this._addWorkerMessagesToConsole(resultRender.messages);
Renderer.Get.useMesh(resultRender.result);
}
AppConsole.success('Rendered mesh');
AppConsole.success(LOC('import.rendered_mesh'));
return true;
}
private async _materials(): Promise<boolean> {
AppConsole.info('Updating materials...');
AppConsole.info(LOC('materials.updating_materials'));
{
// Instruct the worker to perform the job and await the result
const resultMaterials = await this._workerController.execute({
@ -146,7 +150,7 @@ export class AppContext {
this._addWorkerMessagesToConsole(resultMaterials.messages);
}
AppConsole.success('Updated materials');
AppConsole.success(LOC('materials.updated_materials'));
return true;
}
@ -155,7 +159,7 @@ export class AppContext {
// Gather data from the UI to send to the worker
const components = UI.Get.layout.voxelise.components;
AppConsole.info('Loading voxel mesh...');
AppConsole.info(LOC('voxelise.loading_voxel_mesh'));
{
// Instruct the worker to perform the job and await the result
const resultVoxelise = await this._workerController.execute({
@ -178,9 +182,9 @@ export class AppContext {
this._addWorkerMessagesToConsole(resultVoxelise.messages);
}
AppConsole.success('Loaded voxel mesh');
AppConsole.success(LOC('voxelise.loaded_voxel_mesh'));
AppConsole.info('Rendering voxel mesh...');
AppConsole.info(LOC('voxelise.rendering_voxel_mesh'));
{
let moreVoxelsToBuffer = false;
do {
@ -205,7 +209,7 @@ export class AppContext {
Renderer.Get.useVoxelMeshChunk(resultRender.result);
} while (moreVoxelsToBuffer);
}
AppConsole.success('Rendered voxel mesh');
AppConsole.success(LOC('voxelise.rendered_voxel_mesh'));
return true;
}
@ -214,7 +218,7 @@ export class AppContext {
// Gather data from the UI to send to the worker
const components = UI.Get.layout.assign.components;
AppConsole.info('Loading block mesh...');
AppConsole.info(LOC('assign.loading_block_mesh'));
{
// Instruct the worker to perform the job and await the result
const resultAssign = await this._workerController.execute({
@ -242,9 +246,9 @@ export class AppContext {
this._addWorkerMessagesToConsole(resultAssign.messages);
}
AppConsole.success('Loaded block mesh');
AppConsole.success(LOC('assign.loaded_block_mesh'));
AppConsole.info('Rendering block mesh...');
AppConsole.info(LOC('assign.rendering_block_mesh'));
{
let moreBlocksToBuffer = false;
do {
@ -268,7 +272,7 @@ export class AppContext {
Renderer.Get.useBlockMeshChunk(resultRender.result);
} while (moreBlocksToBuffer);
}
AppConsole.success('Rendered voxel mesh');
AppConsole.success(LOC('assign.rendered_block_mesh'));
return true;
}
@ -277,7 +281,7 @@ export class AppContext {
// Gather data from the UI to send to the worker
const components = UI.Get.layout.export.components;
AppConsole.info('Exporting structure...');
AppConsole.info(LOC('export.exporting_structure'));
{
// Instruct the worker to perform the job and await the result
const resultExport = await this._workerController.execute({
@ -296,7 +300,7 @@ export class AppContext {
this._addWorkerMessagesToConsole(resultExport.messages);
download(resultExport.result.buffer, 'result.' + resultExport.result.extension);
}
AppConsole.success('Exported structure');
AppConsole.success(LOC('export.exported_structure'));
return true;
}
@ -307,10 +311,10 @@ export class AppContext {
*/
private _handleErrors(result: TFromWorkerMessage) {
if (result.action === 'KnownError') {
AppConsole.error(result.error.message);
AppConsole.error(result.error.message as TLocalisedString);
return true;
} else if (result.action === 'UnknownError') {
AppConsole.error('Something unexpectedly went wrong');
AppConsole.error(LOC('something_went_wrong'));
LOG_ERROR(result.error);
return true;
}

View File

@ -3,8 +3,7 @@ import path from 'path';
import ATLAS_VANILLA from '../res/atlases/vanilla.atlas';
import { RGBA } from './colour';
import { AppTypes, AppUtil, TOptional, UV } from './util';
import { AppError, ASSERT } from './util/error_util';
import { LOG } from './util/log_util';
import { ASSERT } from './util/error_util';
import { AppPaths } from './util/path_util';
export type TAtlasBlockFace = {
@ -56,9 +55,7 @@ export class Atlas {
const atlasJSON = JSON.parse(ATLAS_VANILLA);
if (atlasJSON.formatVersion !== 3) {
throw new AppError(`The '${atlasName}' texture atlas uses an outdated format and needs to be recreated`);
}
ASSERT(atlasJSON.formatVersion === 3, `The '${atlasName}' texture atlas uses an outdated format and needs to be recreated`);
const atlasData = atlasJSON;
atlas._atlasSize = atlasData.atlasSize;

View File

@ -6,6 +6,7 @@ import { RGBA_255, RGBAUtil } from './colour';
import { AppRuntimeConstants } from './constants';
import { Ditherer } from './dither';
import { BlockMeshLighting } from './lighting';
import { LOC } from './localiser';
import { Palette } from './palette';
import { ProgressManager } from './progress';
import { StatusHandler } from './status';
@ -177,7 +178,7 @@ export class BlockMesh {
ProgressManager.Get.end(taskHandle);
if (blockMeshParams.fallable === 'do-nothing' && countFalling > 0) {
StatusHandler.warning(`${countFalling.toLocaleString()} blocks will fall under gravity when this structure is placed`);
StatusHandler.warning(LOC('assign.falling_blocks', { count: countFalling }));
}
}
@ -217,7 +218,7 @@ export class BlockMesh {
return true;
}
throw new AppError('Block palette contains no light blocks to place');
throw new AppError(LOC('assign.block_palette_missing_light_blocks'));
}
public getBlockAt(pos: Vector3): TOptional<Block> {
@ -236,9 +237,7 @@ export class BlockMesh {
}
public getVoxelMesh() {
if (!this._voxelMesh) {
throw new AppError('Could not get voxel mesh');
}
ASSERT(this._voxelMesh !== undefined, 'Block mesh has no voxel mesh');
return this._voxelMesh;
}

View File

@ -1,3 +1,4 @@
import { TLocales } from '../loc/base';
import { RGBA } from './colour';
import { LOG } from './util/log_util';
@ -15,6 +16,7 @@ export class AppConfig {
public readonly VERSION_TYPE: 'd' | 'a' | 'r' = 'r'; // dev, alpha, or release build
public readonly MINECRAFT_VERSION = '1.19.3';
public readonly LOCALE: TLocales = 'en_GB';
public readonly VOXEL_BUFFER_CHUNK_SIZE = 5_000;
public readonly AMBIENT_OCCLUSION_OVERRIDE_CORNER = true;
public readonly USE_WORKER_THREAD = true;

View File

@ -2,6 +2,7 @@ import { NBT, TagType } from 'prismarine-nbt';
import { BlockMesh } from '../block_mesh';
import { AppConstants } from '../constants';
import { LOC } from '../localiser';
import { StatusHandler } from '../status';
import { AppUtil } from '../util';
import { saveNBT } from '../util/nbt_util';
@ -22,7 +23,7 @@ export class NBTExporter extends IExporter {
const isTooBig = sizeVector.x > 48 && sizeVector.y > 48 && sizeVector.z > 48;
if (isTooBig) {
StatusHandler.warning('Structure blocks only support structures of size 48x48x48, blocks outside this range will be removed');
StatusHandler.warning(LOC('export.nbt_exporter_too_big'));
}
const blockNameToIndex = new Map<string, number>();

View File

@ -4,6 +4,7 @@ import { NBT, TagType } from 'prismarine-nbt';
import { BLOCK_IDS } from '../../res/block_ids';
import { BlockMesh } from '../block_mesh';
import { LOC } from '../localiser';
import { StatusHandler } from '../status';
import { LOG_WARN } from '../util/log_util';
import { saveNBT } from '../util/nbt_util';
@ -53,9 +54,7 @@ export class Schematic extends IExporter {
}
if (unsupportedBlocks.size > 0) {
StatusHandler.warning(
`${numBlocksUnsupported} blocks (${unsupportedBlocks.size} unique) are not supported by the .schematic format, Stone block are used in their place. Try using the schematic-friendly palette, or export using .litematica`,
);
StatusHandler.warning(LOC('export.schematic_unsupported_blocks', { count: numBlocksUnsupported, unique: unsupportedBlocks.size }));
LOG_WARN(unsupportedBlocks);
}

View File

@ -1,3 +1,4 @@
import { LOC } from '../localiser';
import { checkNaN } from '../math';
import { Mesh, Tri } from '../mesh';
import { UV } from '../util';
@ -136,7 +137,7 @@ export class ObjImporter extends IImporter {
break;
}
default:
throw new AppError(`Face data has unexpected number of vertex data: ${vertexData.length}`);
throw new AppError(LOC('import.invalid_face_data', { count: vertexData.length}));
}
}
@ -178,7 +179,7 @@ export class ObjImporter extends IImporter {
return new Promise((res, rej) => {
file.text().then((fileSource) => {
if (fileSource.includes('<27>')) {
rej(new AppError(`Unrecognised character found, please encode using UTF-8`));
rej(new AppError(LOC('import.invalid_encoding')));
}
fileSource.replace('\r', ''); // Convert Windows carriage return
@ -202,7 +203,7 @@ export class ObjImporter extends IImporter {
parser.delegate(match.groups);
} catch (error) {
if (error instanceof AppError) {
throw new AppError(`Failed attempt to parse '${line}', because '${error.message}'`);
throw new AppError(LOC('import.failed_to_parse_line', { line: line, error: error.message }));
}
}
return;
@ -213,7 +214,7 @@ export class ObjImporter extends IImporter {
return line.startsWith(token);
});
if (beginsWithEssentialToken) {
throw new AppError(`Failed to parse essential token for <b>${line}</b>`);
ASSERT(false, `Failed to parse essential token for <b>${line}</b>`);
}
}
}

47
src/localiser.ts Normal file
View File

@ -0,0 +1,47 @@
import i18next from 'i18next';
import { locales, TTranslationMap } from '../loc/base';
import { AppConfig } from './config';
import { ASSERT } from './util/error_util';
import { TBrand } from './util/type_util';
// https://stackoverflow.com/questions/58277973/how-to-type-check-i18n-dictionaries-with-typescript
// get all possible key paths
type DeepKeys<T> = T extends object ? {
[K in keyof T]-?: `${K & string}` | Concat<K & string, DeepKeys<T[K]>>
}[keyof T] : '';
// or: only get leaf and no intermediate key path
type DeepLeafKeys<T> = T extends object ?
{ [K in keyof T]-?: Concat<K & string, DeepKeys<T[K]>> }[keyof T] : '';
// https://stackoverflow.com/questions/58277973/how-to-type-check-i18n-dictionaries-with-typescript
type Concat<K extends string, P extends string> =
`${K}${'' extends P ? '' : '.'}${P}`
export type TLocalisedString = TBrand<string, 'loc'>;
export class Localiser {
/* Singleton */
private static _instance: Localiser;
public static get Get() {
return this._instance || (this._instance = new this());
}
public async init() {
await i18next.init({
lng: AppConfig.Get.LOCALE,
fallbackLng: 'en_GB',
debug: true,
resources: locales,
});
ASSERT(i18next.isInitialized, 'i18next not initialised');
}
public translate<P extends DeepLeafKeys<TTranslationMap>>(p: P, options?: any): TLocalisedString {
return (i18next.t(p, options) as unknown) as TLocalisedString;
}
}
export const LOC = Localiser.Get.translate;

View File

@ -1,5 +1,4 @@
import { AppError } from './util/error_util';
import { LOG_ERROR } from './util/log_util';
import { ASSERT } from './util/error_util';
import { Vector3 } from './vector';
export namespace AppMath {
@ -69,22 +68,7 @@ export const checkNaN = (...args: number[]) => {
const existsNaN = args.some((arg) => {
return isNaN(arg);
});
if (existsNaN) {
LOG_ERROR(args);
throw new AppError('Found NaN');
}
};
/**
* Throws if any number in not within [0, 1]
*/
export const checkFractional = (...args: number[]) => {
const existsOutside = args.some((arg) => {
return arg > 1.0 || arg < 0.0;
});
if (existsOutside) {
throw new AppError('Found value outside of [0, 1]');
}
ASSERT(!existsNaN, 'Found NaN');
};
export const degreesToRadians = Math.PI / 180;

View File

@ -2,6 +2,7 @@ import path from 'path';
import { Bounds } from './bounds';
import { RGBA, RGBAColours, RGBAUtil } from './colour';
import { LOC } from './localiser';
import { degreesToRadians } from './math';
import { StatusHandler } from './status';
import { Texture, TextureConverter, TImageFiletype, TImageRawWrap, TTransparencyOptions } from './texture';
@ -144,18 +145,18 @@ export class Mesh {
// TODO: Check indices exist
if (this._vertices.length === 0) {
throw new AppError('No vertices were loaded');
throw new AppError(LOC('import.no_vertices_loaded'));
}
if (this._tris.length === 0) {
throw new AppError('No triangles were loaded');
throw new AppError(LOC('import.no_triangles_loaded'));
}
if (this._tris.length >= 100_000) {
StatusHandler.warning(`The imported mesh has ${this._tris.length.toLocaleString()} triangles, consider simplifying it in a DDC such as Blender`);
StatusHandler.warning(LOC('import.too_many_triangles', { count: this._tris.length }));
}
StatusHandler.info(`${this._vertices.length.toLocaleString()} vertices, ${this._tris.length.toLocaleString()} triangles`);
StatusHandler.info(LOC('import.vertex_triangle_count', { vertex_count: this._vertices.length, triangle_count: this._tris.length }));
// Give warning if normals are not defined
let giveNormalsWarning = false;
@ -176,7 +177,7 @@ export class Mesh {
}
}
if (giveNormalsWarning) {
StatusHandler.warning('Some vertices do not have their normals defined, this may cause voxels to be aligned incorrectly');
StatusHandler.warning(LOC('import.missing_normals'));
};
}
@ -288,10 +289,7 @@ export class Mesh {
private _centreMesh() {
const centre = this.getBounds().getCentre();
if (!centre.isNumber()) {
throw new AppError('Could not find centre of mesh');
}
ASSERT(centre.isNumber(), 'Could not find centre of mesh');
// Translate each triangle
this.translateMesh(centre.negate());
@ -303,7 +301,7 @@ export class Mesh {
const scaleFactor = Mesh.desiredHeight / size.y;
if (isNaN(scaleFactor) || !isFinite(scaleFactor)) {
throw new AppError('Could not scale mesh correctly - mesh is likely 2D, rotate it so that it has a non-zero height');
throw new AppError(LOC('import.could_not_scale_mesh'));
} else {
this.scaleMesh(scaleFactor);
}

View File

@ -3,6 +3,7 @@ import { PALETTE_COLOURFUL } from '../res/palettes/colourful';
import { PALETTE_GREYSCALE } from '../res/palettes/greyscale';
import { PALETTE_SCHEMATIC_FRIENDLY } from '../res/palettes/schematic-friendly';
import { Atlas } from './atlas';
import { LOC } from './localiser';
import { StatusHandler } from './status';
import { AppTypes, AppUtil, TOptional } from './util';
import { LOG_WARN } from './util/log_util';
@ -161,7 +162,7 @@ export class Palette {
}
if (missingBlocks.length > 0) {
StatusHandler.warning(`${missingBlocks.length} palette block(s) are missing atlas textures, they will not be used`);
StatusHandler.warning(LOC('assign.blocks_missing_textures', { count: missingBlocks }));
LOG_WARN('Blocks missing atlas textures', missingBlocks);
}
}

View File

@ -1,3 +1,4 @@
import { TLocalisedString } from './localiser';
import { TMessage } from './ui/console';
import { LOG, LOG_ERROR, LOG_WARN } from './util/log_util';
@ -24,19 +25,19 @@ export class StatusHandler {
this._messages = [];
}
public static success(message: string) {
public static success(message: TLocalisedString) {
this.Get._messages.push({ text: message, type: 'success' });
}
public static info(message: string) {
public static info(message: TLocalisedString) {
this.Get._messages.push({ text: message, type: 'info' });
}
public static warning(message: string) {
public static warning(message: TLocalisedString) {
this.Get._messages.push({ text: message, type: 'warning' });
}
public static error(message: string) {
public static error(message: TLocalisedString) {
this.Get._messages.push({ text: message, type: 'error' });
}

View File

@ -1,4 +1,5 @@
import { PALETTE_ALL_RELEASE } from '../../../res/palettes/all';
import { LOC } from '../../localiser';
import { Palette } from '../../palette';
import { AppUtil } from '../../util';
import { ASSERT } from '../../util/error_util';
@ -64,7 +65,7 @@ export class PaletteComponent extends ConfigComponent<Palette, HTMLDivElement> {
if (files?.length === 1) {
const file = files.item(0);
ASSERT(file !== null);
AppConsole.info(`Reading ${file.name}...`);
AppConsole.info(LOC('assign.reading_palette', { file_name: file.name }));
file.text().then((text) => {
this._onReadPaletteFile(text);
});
@ -100,18 +101,18 @@ export class PaletteComponent extends ConfigComponent<Palette, HTMLDivElement> {
++countDeselected;
}
});
AppConsole.info(`Deselected ${countDeselected} blocks`);
AppConsole.info(LOC('assign.deselected_blocks', { count: countDeselected }));
AppConsole.info(`Found ${blockNames.length} blocks`);
AppConsole.info(LOC('assign.found_blocks', { count: blockNames.length }));
let countChecked = 0;
blockNames.forEach((blockName) => {
if (!AppUtil.Text.isNamespacedBlock(blockName)) {
AppConsole.error(`'${blockName}' is not namespaced correctly, do you mean 'minecraft:${blockName}'?`);
AppConsole.error(LOC('assign.block_not_namespaced', { block_name: blockName }));
} else {
const checkboxIndex = this._checkboxes.findIndex((x) => x.block === blockName);
if (checkboxIndex === -1) {
AppConsole.error(`Could not use '${blockName}' as it is unsupported`);
AppConsole.error(LOC('assign.could_not_use_block', { block_name: blockName }));
} else {
this._checkboxes[checkboxIndex].element.check();
++countChecked;
@ -119,7 +120,7 @@ export class PaletteComponent extends ConfigComponent<Palette, HTMLDivElement> {
}
});
AppConsole.success(`Selected ${countChecked} blocks`);
AppConsole.info(LOC('assign.selected_blocks', { count: countChecked }));
this._onCountSelectedChanged();
}

View File

@ -1,8 +1,9 @@
import { TLocalisedString } from '../localiser';
import { LOG, LOG_ERROR, LOG_WARN } from '../util/log_util';
import { UIUtil } from '../util/ui_util';
import { HTMLBuilder } from './misc';
export type TMessage = { text: string, type: 'success' | 'info' | 'warning' | 'error' };
export type TMessage = { text: TLocalisedString, type: 'success' | 'info' | 'warning' | 'error' };
export class AppConsole {
private static _instance: AppConsole;
@ -77,25 +78,25 @@ export class AppConsole {
}
}
public static success(message: string) {
public static success(message: TLocalisedString) {
LOG(message);
this.Get._messages.push({ text: message, type: 'success' });
this.Get.addLast();
}
public static info(message: string) {
public static info(message: TLocalisedString) {
LOG(message);
this.Get._messages.push({ text: message, type: 'info' });
this.Get.addLast();
}
public static warning(message: string) {
public static warning(message: TLocalisedString) {
LOG_WARN(message);
this.Get._messages.push({ text: message, type: 'warning' });
this.Get.addLast();
}
public static error(message: string) {
public static error(message: TLocalisedString) {
LOG_ERROR(message);
this.Get._messages.push({ text: message, type: 'error' });
this.Get.addLast();

View File

@ -1,11 +1,13 @@
import { TLocalisedString } from '../localiser';
export class AppError extends Error {
constructor(msg: string) {
constructor(msg: TLocalisedString) {
super(msg);
Object.setPrototypeOf(this, AppError.prototype);
}
}
export function ASSERT(condition: any, errorMessage = 'Assertion Failed'): asserts condition {
export function ASSERT(condition: any, errorMessage: string = 'Assertion Failed'): asserts condition {
if (!condition) {
Error(errorMessage);
throw Error(errorMessage);

View File

@ -1,5 +1,6 @@
import { RGBA, RGBAColours, RGBAUtil } from '../colour';
import { AppConfig } from '../config';
import { LOC } from '../localiser';
import { MaterialType, Mesh } from '../mesh';
import { StatusHandler } from '../status';
import { Triangle, UVTriangle } from '../triangle';
@ -13,10 +14,10 @@ export abstract class IVoxeliser {
public voxelise(mesh: Mesh, voxeliseParams: VoxeliseParams.Input): VoxelMesh {
const voxelMesh = this._voxelise(mesh, voxeliseParams);
StatusHandler.info(`Voxel mesh has ${voxelMesh.getVoxelCount().toLocaleString()} voxels`);
StatusHandler.info(LOC('voxelise.voxel_count', { count: voxelMesh.getVoxelCount() }));
const dim = voxelMesh.getBounds().getDimensions().add(1);
StatusHandler.info(`Dimensions are ${dim.x.toLocaleString()}x${dim.y.toLocaleString()}x${dim.z.toLocaleString()} voxels`);
StatusHandler.info(LOC('voxelise.voxel_mesh_dimensions', { x: dim.x, y: dim.y, z: dim.z }));
return voxelMesh;
}

View File

@ -7,6 +7,7 @@ import { EAppEvent, EventManager } from './event';
import { IExporter } from './exporters/base_exporter';
import { ExporterFactory } from './exporters/exporters';
import { ImporterFactor } from './importers/importers';
import { Localiser } from './localiser';
import { Mesh } from './mesh';
import { ProgressManager, TTaskHandle } from './progress';
import { ASSERT } from './util/error_util';
@ -66,6 +67,9 @@ export class WorkerClient {
postMessage(message);
});
// TODO: Async: should await
Localiser.Get.init();
return {};
}

View File

@ -40,9 +40,7 @@ export class WorkerController {
payload: payload,
callback: res,
});
if (!success) {
rej(new AppError('Already performing a job'));
}
ASSERT(success, 'Already performing a job');
});
}