forked from mirror/ObjToSchematic
Reimplemented localisation
This commit is contained in:
parent
9e3dac7e34
commit
d96dddbcf6
@ -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
11
loc/base.ts
Normal 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
59
loc/en_GB.ts
Normal 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
7
loc/en_US.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// Credits:
|
||||
// LucasDower
|
||||
|
||||
import { TTranslationMap } from './base';
|
||||
|
||||
export const en_US: Partial<TTranslationMap> = {
|
||||
};
|
33
package-lock.json
generated
33
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>();
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
47
src/localiser.ts
Normal 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;
|
20
src/math.ts
20
src/math.ts
@ -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;
|
||||
|
18
src/mesh.ts
18
src/mesh.ts
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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' });
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user