Internationalize UI (EN default) + 5-language switcher
English is now the default language. Added src/i18n.ts (t() helper + dictionary) covering all UI strings across App, Updater and ModelViewer, plus a language selector in Settings for English, Русский, Deutsch, Français, Español (persisted in localStorage). Backend error strings translated to English. Also removed the purple highlight on the resize handles (cursor-only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -10,6 +10,7 @@ import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader.js";
|
||||
import { ThreeMFLoader } from "three/examples/jsm/loaders/3MFLoader.js";
|
||||
import { RoomEnvironment } from "three/examples/jsm/environments/RoomEnvironment.js";
|
||||
import { p4 } from "./p4";
|
||||
import { t } from "./i18n";
|
||||
|
||||
type Api = { toggleWire: (b: boolean) => void; toggleGrid: (b: boolean) => void; reset: () => void };
|
||||
|
||||
@ -143,11 +144,11 @@ export default function ModelViewer({ depot, name }: { depot: string; name: stri
|
||||
if (ext === "glb" || ext === "gltf") new GLTFLoader(manager).parse(buf, "", (g) => !disposed && place(g.scene), fail);
|
||||
else if (ext === "obj") place(new OBJLoader(manager).parse(new TextDecoder().decode(buf)));
|
||||
else if (ext === "fbx") place(new FBXLoader(manager).parse(buf, ""));
|
||||
else if (ext === "dae") { const c = new ColladaLoader(manager).parse(new TextDecoder().decode(buf), ""); if (c && c.scene) place(c.scene); else fail("Collada: пустая сцена"); }
|
||||
else if (ext === "dae") { const c = new ColladaLoader(manager).parse(new TextDecoder().decode(buf), ""); if (c && c.scene) place(c.scene); else fail(t("Collada: empty scene")); }
|
||||
else if (ext === "3mf") place(new ThreeMFLoader(manager).parse(buf));
|
||||
else if (ext === "stl") place(new THREE.Mesh(new STLLoader().parse(buf), new THREE.MeshStandardMaterial({ color: 0x9a8bf9, roughness: 0.5 })));
|
||||
else if (ext === "ply") { const g = new PLYLoader().parse(buf); g.computeVertexNormals(); place(new THREE.Mesh(g, new THREE.MeshStandardMaterial({ color: 0x9a8bf9, roughness: 0.5, vertexColors: !!g.getAttribute("color") }))); }
|
||||
else { setErr("Формат не поддерживается вьювером"); setStatus("error"); }
|
||||
else { setErr(t("Format not supported by the viewer")); setStatus("error"); }
|
||||
} catch (e) { fail(e); }
|
||||
})();
|
||||
|
||||
@ -185,20 +186,20 @@ export default function ModelViewer({ depot, name }: { depot: string; name: stri
|
||||
</span>
|
||||
{status === "ok" && (
|
||||
<div className="v3d-tools">
|
||||
<button className={"v3d-btn" + (wire ? " on" : "")} onClick={() => { setWire(!wire); api.current?.toggleWire(!wire); }} title="Wireframe">◫</button>
|
||||
<button className={"v3d-btn" + (grid ? " on" : "")} onClick={() => { setGrid(!grid); api.current?.toggleGrid(!grid); }} title="Сетка">▦</button>
|
||||
<button className="v3d-btn" onClick={() => api.current?.reset()} title="Сбросить вид">⟲</button>
|
||||
<button className={"v3d-btn" + (wire ? " on" : "")} onClick={() => { setWire(!wire); api.current?.toggleWire(!wire); }} title={t("Wireframe")}>◫</button>
|
||||
<button className={"v3d-btn" + (grid ? " on" : "")} onClick={() => { setGrid(!grid); api.current?.toggleGrid(!grid); }} title={t("Grid")}>▦</button>
|
||||
<button className="v3d-btn" onClick={() => api.current?.reset()} title={t("Reset view")}>⟲</button>
|
||||
</div>
|
||||
)}
|
||||
{status === "loading" && <div className="viewer-msg"><span className="ldr" />Загрузка модели…</div>}
|
||||
{status === "loading" && <div className="viewer-msg"><span className="ldr" />{t("Loading model…")}</div>}
|
||||
{status === "error" && <div className="viewer-msg err">{err}</div>}
|
||||
{status === "ok" && <span className="hint">тащи — вращать · колесо — зум · ПКМ — панорама</span>}
|
||||
{status === "ok" && <span className="hint">{t("drag — rotate · wheel — zoom · right-click — pan")}</span>}
|
||||
</div>
|
||||
<div className="assetmeta">
|
||||
<div className="mi"><span className="k">Треугольники</span><span className="v">{fmt(stats.tris)}</span></div>
|
||||
<div className="mi"><span className="k">Вершины</span><span className="v">{fmt(stats.verts)}</span></div>
|
||||
<div className="mi"><span className="k">Меши · Материалы</span><span className="v">{stats.meshes} · {stats.mats}</span></div>
|
||||
<div className="mi"><span className="k">Рендер</span><span className="v acc">three.js · PBR</span></div>
|
||||
<div className="mi"><span className="k">{t("Triangles")}</span><span className="v">{fmt(stats.tris)}</span></div>
|
||||
<div className="mi"><span className="k">{t("Vertices")}</span><span className="v">{fmt(stats.verts)}</span></div>
|
||||
<div className="mi"><span className="k">{t("Meshes · Materials")}</span><span className="v">{stats.meshes} · {stats.mats}</span></div>
|
||||
<div className="mi"><span className="k">{t("Render")}</span><span className="v acc">three.js · PBR</span></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user