Replace i18n by a custom implementation (#638)
There are two problems with the current localization strategy: - We download the translations on-demand, which means that it does a serial roundtrip for nothing. - withTranslation helper actually renders the app 3 times on startup, instead of once (I haven't tried to debug it)
This commit is contained in:
parent
637276301a
commit
e4919e2e6c
47
package-lock.json
generated
47
package-lock.json
generated
@ -1681,15 +1681,6 @@
|
|||||||
"csstype": "^2.2.0"
|
"csstype": "^2.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/react-color": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-J6mYm43Sid9y+OjZ7NDfJ2VVkeeuTPNVImNFITgQNXodHteKfl/t/5pAR5Z9buodZ2tCctsZjgiMlQOpfntakw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@types/react": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/react-dom": {
|
"@types/react-dom": {
|
||||||
"version": "16.9.5",
|
"version": "16.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.5.tgz",
|
||||||
@ -7123,14 +7114,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"html-parse-stringify2": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-3FZwtyksoVi3vJFsmmc1rIhyg0o=",
|
|
||||||
"requires": {
|
|
||||||
"void-elements": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"html-webpack-plugin": {
|
"html-webpack-plugin": {
|
||||||
"version": "4.0.0-beta.5",
|
"version": "4.0.0-beta.5",
|
||||||
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz",
|
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.0.0-beta.5.tgz",
|
||||||
@ -7412,14 +7395,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"i18next": {
|
|
||||||
"version": "19.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.1.0.tgz",
|
|
||||||
"integrity": "sha512-ISbmukX4L6Dz0QoH9+EW1AnBw7j+NRLoMu9uLPMaNSSTP9Eie9/oUL0dOyWX15baB3gYOpkHJpGZRHOqcnl0ew==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"i18next-browser-languagedetector": {
|
"i18next-browser-languagedetector": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-4.0.1.tgz",
|
||||||
@ -7428,14 +7403,6 @@
|
|||||||
"@babel/runtime": "^7.5.5"
|
"@babel/runtime": "^7.5.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"i18next-xhr-backend": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/i18next-xhr-backend/-/i18next-xhr-backend-3.2.2.tgz",
|
|
||||||
"integrity": "sha512-OtRf2Vo3IqAxsttQbpjYnmMML12IMB5e0fc5B7qKJFLScitYaXa1OhMX0n0X/3vrfFlpHL9Ro/H+ps4Ej2j7QQ==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.5.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@ -12928,15 +12895,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.4.tgz",
|
||||||
"integrity": "sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA=="
|
"integrity": "sha512-ueZzLmHltszTshDMwyfELDq8zOA803wQ1ZuzCccXa1m57k1PxSHfflPD5W9YIiTXLs0JTLzoj6o1LuM5N6zzNA=="
|
||||||
},
|
},
|
||||||
"react-i18next": {
|
|
||||||
"version": "11.3.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.3.1.tgz",
|
|
||||||
"integrity": "sha512-S/CWHcnew1lXo8HeniGhBU5kTmPhZ4w4rtA4m/gDN07soCtKKYSAcLNm7zhwjI2OSR4Skd0vOtzNp/FzEEjxIw==",
|
|
||||||
"requires": {
|
|
||||||
"@babel/runtime": "^7.3.1",
|
|
||||||
"html-parse-stringify2": "2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.12.0",
|
"version": "16.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz",
|
||||||
@ -15969,11 +15927,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
|
||||||
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
|
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
|
||||||
},
|
},
|
||||||
"void-elements": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w="
|
|
||||||
},
|
|
||||||
"w3c-hr-time": {
|
"w3c-hr-time": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
|
||||||
|
@ -7,13 +7,10 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"browser-nativefs": "0.2.0",
|
"browser-nativefs": "0.2.0",
|
||||||
"i18next": "19.1.0",
|
|
||||||
"i18next-browser-languagedetector": "4.0.1",
|
"i18next-browser-languagedetector": "4.0.1",
|
||||||
"i18next-xhr-backend": "3.2.2",
|
|
||||||
"nanoid": "2.1.10",
|
"nanoid": "2.1.10",
|
||||||
"react": "16.12.0",
|
"react": "16.12.0",
|
||||||
"react-dom": "16.12.0",
|
"react-dom": "16.12.0",
|
||||||
"react-i18next": "11.3.1",
|
|
||||||
"react-scripts": "3.3.0",
|
"react-scripts": "3.3.0",
|
||||||
"roughjs": "4.0.4"
|
"roughjs": "4.0.4"
|
||||||
},
|
},
|
||||||
@ -24,7 +21,6 @@
|
|||||||
"@types/jest": "25.1.0",
|
"@types/jest": "25.1.0",
|
||||||
"@types/nanoid": "2.1.0",
|
"@types/nanoid": "2.1.0",
|
||||||
"@types/react": "16.9.19",
|
"@types/react": "16.9.19",
|
||||||
"@types/react-color": "3.0.1",
|
|
||||||
"@types/react-dom": "16.9.5",
|
"@types/react-dom": "16.9.5",
|
||||||
"enzyme": "3.11.0",
|
"enzyme": "3.11.0",
|
||||||
"enzyme-adapter-react-16": "1.15.2",
|
"enzyme-adapter-react-16": "1.15.2",
|
||||||
|
@ -4,13 +4,14 @@ import { ColorPicker } from "../components/ColorPicker";
|
|||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import { trash } from "../components/icons";
|
import { trash } from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
|
||||||
export const actionChangeViewBackgroundColor: Action = {
|
export const actionChangeViewBackgroundColor: Action = {
|
||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return { appState: { ...appState, viewBackgroundColor: value } };
|
return { appState: { ...appState, viewBackgroundColor: value } };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData, t }) => {
|
PanelComponent: ({ appState, updateData }) => {
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative" }}>
|
<div style={{ position: "relative" }}>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
@ -32,7 +33,7 @@ export const actionClearCanvas: Action = {
|
|||||||
appState: getDefaultAppState(),
|
appState: getDefaultAppState(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData, t }) => (
|
PanelComponent: ({ updateData }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={trash}
|
icon={trash}
|
||||||
|
@ -4,13 +4,14 @@ import { ProjectName } from "../components/ProjectName";
|
|||||||
import { saveAsJSON, loadFromJSON } from "../scene";
|
import { saveAsJSON, loadFromJSON } from "../scene";
|
||||||
import { load, save } from "../components/icons";
|
import { load, save } from "../components/icons";
|
||||||
import { ToolButton } from "../components/ToolButton";
|
import { ToolButton } from "../components/ToolButton";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
|
||||||
export const actionChangeProjectName: Action = {
|
export const actionChangeProjectName: Action = {
|
||||||
name: "changeProjectName",
|
name: "changeProjectName",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return { appState: { ...appState, name: value } };
|
return { appState: { ...appState, name: value } };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData, t }) => (
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
<ProjectName
|
<ProjectName
|
||||||
label={t("labels.fileTitle")}
|
label={t("labels.fileTitle")}
|
||||||
value={appState.name || "Unnamed"}
|
value={appState.name || "Unnamed"}
|
||||||
@ -24,7 +25,7 @@ export const actionChangeExportBackground: Action = {
|
|||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return { appState: { ...appState, exportBackground: value } };
|
return { appState: { ...appState, exportBackground: value } };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData, t }) => (
|
PanelComponent: ({ appState, updateData }) => (
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -44,7 +45,7 @@ export const actionSaveScene: Action = {
|
|||||||
saveAsJSON(elements, appState).catch(err => console.error(err));
|
saveAsJSON(elements, appState).catch(err => console.error(err));
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData, t }) => (
|
PanelComponent: ({ updateData }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={save}
|
icon={save}
|
||||||
@ -64,7 +65,7 @@ export const actionLoadScene: Action = {
|
|||||||
) => {
|
) => {
|
||||||
return { elements: loadedElements, appState: loadedAppState };
|
return { elements: loadedElements, appState: loadedAppState };
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData, t }) => (
|
PanelComponent: ({ updateData }) => (
|
||||||
<ToolButton
|
<ToolButton
|
||||||
type="button"
|
type="button"
|
||||||
icon={load}
|
icon={load}
|
||||||
|
@ -6,6 +6,7 @@ import { ButtonSelect } from "../components/ButtonSelect";
|
|||||||
import { isTextElement, redrawTextBoundingBox } from "../element";
|
import { isTextElement, redrawTextBoundingBox } from "../element";
|
||||||
import { ColorPicker } from "../components/ColorPicker";
|
import { ColorPicker } from "../components/ColorPicker";
|
||||||
import { AppState } from "../../src/types";
|
import { AppState } from "../../src/types";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
|
||||||
const changeProperty = (
|
const changeProperty = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
@ -46,7 +47,7 @@ export const actionChangeStrokeColor: Action = {
|
|||||||
appState: { ...appState, currentItemStrokeColor: value },
|
appState: { ...appState, currentItemStrokeColor: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<>
|
<>
|
||||||
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
@ -76,7 +77,7 @@ export const actionChangeBackgroundColor: Action = {
|
|||||||
appState: { ...appState, currentItemBackgroundColor: value },
|
appState: { ...appState, currentItemBackgroundColor: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<>
|
<>
|
||||||
<h3 aria-hidden="true">{t("labels.background")}</h3>
|
<h3 aria-hidden="true">{t("labels.background")}</h3>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
@ -106,7 +107,7 @@ export const actionChangeFillStyle: Action = {
|
|||||||
appState: { ...appState, currentItemFillStyle: value },
|
appState: { ...appState, currentItemFillStyle: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.fill")}</legend>
|
<legend>{t("labels.fill")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
@ -142,7 +143,7 @@ export const actionChangeStrokeWidth: Action = {
|
|||||||
appState: { ...appState, currentItemStrokeWidth: value },
|
appState: { ...appState, currentItemStrokeWidth: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.strokeWidth")}</legend>
|
<legend>{t("labels.strokeWidth")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
@ -176,7 +177,7 @@ export const actionChangeSloppiness: Action = {
|
|||||||
appState: { ...appState, currentItemRoughness: value },
|
appState: { ...appState, currentItemRoughness: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.sloppiness")}</legend>
|
<legend>{t("labels.sloppiness")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
@ -210,7 +211,7 @@ export const actionChangeOpacity: Action = {
|
|||||||
appState: { ...appState, currentItemOpacity: value },
|
appState: { ...appState, currentItemOpacity: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<label className="control-label">
|
<label className="control-label">
|
||||||
{t("labels.opacity")}
|
{t("labels.opacity")}
|
||||||
<input
|
<input
|
||||||
@ -256,7 +257,7 @@ export const actionChangeFontSize: Action = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.fontSize")}</legend>
|
<legend>{t("labels.fontSize")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
@ -304,7 +305,7 @@ export const actionChangeFontFamily: Action = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{t("labels.fontFamily")}</legend>
|
<legend>{t("labels.fontFamily")}</legend>
|
||||||
<ButtonSelect
|
<ButtonSelect
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
} from "./types";
|
} from "./types";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { TFunction } from "i18next";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
export class ActionManager implements ActionsManagerInterface {
|
export class ActionManager implements ActionsManagerInterface {
|
||||||
actions: { [keyProp: string]: Action } = {};
|
actions: { [keyProp: string]: Action } = {};
|
||||||
@ -48,7 +48,6 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
actionFilter: ActionFilterFn = action => action,
|
actionFilter: ActionFilterFn = action => action,
|
||||||
t?: TFunction,
|
|
||||||
) {
|
) {
|
||||||
return Object.values(this.actions)
|
return Object.values(this.actions)
|
||||||
.filter(actionFilter)
|
.filter(actionFilter)
|
||||||
@ -59,10 +58,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
(b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999),
|
(b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999),
|
||||||
)
|
)
|
||||||
.map(action => ({
|
.map(action => ({
|
||||||
label:
|
label: action.contextItemLabel ? t(action.contextItemLabel) : "",
|
||||||
t && action.contextItemLabel
|
|
||||||
? t(action.contextItemLabel)
|
|
||||||
: action.contextItemLabel!,
|
|
||||||
action: () => {
|
action: () => {
|
||||||
updater(action.perform(elements, appState, null));
|
updater(action.perform(elements, appState, null));
|
||||||
},
|
},
|
||||||
@ -74,7 +70,6 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
t: TFunction,
|
|
||||||
) {
|
) {
|
||||||
if (this.actions[name] && "PanelComponent" in this.actions[name]) {
|
if (this.actions[name] && "PanelComponent" in this.actions[name]) {
|
||||||
const action = this.actions[name];
|
const action = this.actions[name];
|
||||||
@ -88,7 +83,6 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
elements={elements}
|
elements={elements}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
updateData={updateData}
|
updateData={updateData}
|
||||||
t={t}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { TFunction } from "i18next";
|
|
||||||
|
|
||||||
export type ActionResult = {
|
export type ActionResult = {
|
||||||
elements?: ExcalidrawElement[];
|
elements?: ExcalidrawElement[];
|
||||||
@ -23,7 +22,6 @@ export interface Action {
|
|||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
updateData: (formData: any) => void;
|
updateData: (formData: any) => void;
|
||||||
t: TFunction;
|
|
||||||
}>;
|
}>;
|
||||||
perform: ActionFn;
|
perform: ActionFn;
|
||||||
keyPriority?: number;
|
keyPriority?: number;
|
||||||
@ -57,6 +55,5 @@ export interface ActionsManagerInterface {
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
t: TFunction,
|
|
||||||
) => React.ReactElement | null;
|
) => React.ReactElement | null;
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,7 @@ import { Popover } from "./Popover";
|
|||||||
|
|
||||||
import "./ColorPicker.css";
|
import "./ColorPicker.css";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
import { useTranslation } from "react-i18next";
|
import { t } from "../i18n";
|
||||||
import { TFunction } from "i18next";
|
|
||||||
|
|
||||||
// This is a narrow reimplementation of the awesome react-color Twitter component
|
// This is a narrow reimplementation of the awesome react-color Twitter component
|
||||||
// https://github.com/casesandberg/react-color/blob/master/src/components/twitter/Twitter.js
|
// https://github.com/casesandberg/react-color/blob/master/src/components/twitter/Twitter.js
|
||||||
@ -15,14 +14,12 @@ const Picker = function({
|
|||||||
onChange,
|
onChange,
|
||||||
onClose,
|
onClose,
|
||||||
label,
|
label,
|
||||||
t,
|
|
||||||
}: {
|
}: {
|
||||||
colors: string[];
|
colors: string[];
|
||||||
color: string | null;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
label: string;
|
label: string;
|
||||||
t: TFunction;
|
|
||||||
}) {
|
}) {
|
||||||
const firstItem = React.useRef<HTMLButtonElement>();
|
const firstItem = React.useRef<HTMLButtonElement>();
|
||||||
const colorInput = React.useRef<HTMLInputElement>();
|
const colorInput = React.useRef<HTMLInputElement>();
|
||||||
@ -158,8 +155,6 @@ export function ColorPicker({
|
|||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
label: string;
|
label: string;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [isActive, setActive] = React.useState(false);
|
const [isActive, setActive] = React.useState(false);
|
||||||
const pickerButton = React.useRef<HTMLButtonElement>(null);
|
const pickerButton = React.useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
@ -195,7 +190,6 @@ export function ColorPicker({
|
|||||||
pickerButton.current?.focus();
|
pickerButton.current?.focus();
|
||||||
}}
|
}}
|
||||||
label={label}
|
label={label}
|
||||||
t={t}
|
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -11,8 +11,8 @@ import { AppState } from "../types";
|
|||||||
import { exportToCanvas } from "../scene/export";
|
import { exportToCanvas } from "../scene/export";
|
||||||
import { ActionsManagerInterface, UpdaterFn } from "../actions/types";
|
import { ActionsManagerInterface, UpdaterFn } from "../actions/types";
|
||||||
import Stack from "./Stack";
|
import Stack from "./Stack";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
|
|
||||||
const probablySupportsClipboard =
|
const probablySupportsClipboard =
|
||||||
@ -52,7 +52,6 @@ function ExportModal({
|
|||||||
onExportToBackend: ExportCB;
|
onExportToBackend: ExportCB;
|
||||||
onCloseRequest: () => void;
|
onCloseRequest: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const someElementIsSelected = elements.some(element => element.isSelected);
|
const someElementIsSelected = elements.some(element => element.isSelected);
|
||||||
const [scale, setScale] = useState(defaultScale);
|
const [scale, setScale] = useState(defaultScale);
|
||||||
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
||||||
@ -170,7 +169,6 @@ function ExportModal({
|
|||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
syncActionResult,
|
syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
<Stack.Col gap={1}>
|
<Stack.Col gap={1}>
|
||||||
<div className="ExportDialog__scales">
|
<div className="ExportDialog__scales">
|
||||||
@ -195,7 +193,6 @@ function ExportModal({
|
|||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
syncActionResult,
|
syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
{someElementIsSelected && (
|
{someElementIsSelected && (
|
||||||
<div>
|
<div>
|
||||||
@ -238,7 +235,6 @@ export function ExportDialog({
|
|||||||
onExportToClipboard: ExportCB;
|
onExportToClipboard: ExportCB;
|
||||||
onExportToBackend: ExportCB;
|
onExportToBackend: ExportCB;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
const [modalIsShown, setModalIsShown] = useState(false);
|
const [modalIsShown, setModalIsShown] = useState(false);
|
||||||
const triggerButton = useRef<HTMLButtonElement>(null);
|
const triggerButton = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
export function LanguageList<T>({
|
export function LanguageList<T>({
|
||||||
onClick,
|
onChange,
|
||||||
languages,
|
languages,
|
||||||
currentLanguage,
|
currentLanguage,
|
||||||
}: {
|
}: {
|
||||||
languages: { lng: string; label: string }[];
|
languages: { lng: string; label: string }[];
|
||||||
onClick: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
currentLanguage: string;
|
currentLanguage: string;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<select
|
<select
|
||||||
className="language-select"
|
className="language-select"
|
||||||
onChange={({ target }) => onClick(target.value)}
|
onChange={({ target }) => onChange(target.value)}
|
||||||
value={currentLanguage}
|
value={currentLanguage}
|
||||||
aria-label={t("buttons.selectLanguage")}
|
aria-label={t("buttons.selectLanguage")}
|
||||||
>
|
>
|
||||||
|
@ -6,10 +6,6 @@ import { PreviousScene } from "../scene/types";
|
|||||||
|
|
||||||
Enzyme.configure({ adapter: new Adapter() });
|
Enzyme.configure({ adapter: new Adapter() });
|
||||||
|
|
||||||
jest.mock("react-i18next", () => ({
|
|
||||||
useTranslation: () => ({ t: (key: any) => key }),
|
|
||||||
}));
|
|
||||||
|
|
||||||
function setup(props: any) {
|
function setup(props: any) {
|
||||||
const currentProps = {
|
const currentProps = {
|
||||||
...props,
|
...props,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { PreviousScene } from "../scene/types";
|
import { PreviousScene } from "../scene/types";
|
||||||
|
import { t } from "../i18n";
|
||||||
|
|
||||||
interface StoredScenesListProps {
|
interface StoredScenesListProps {
|
||||||
scenes: PreviousScene[];
|
scenes: PreviousScene[];
|
||||||
@ -13,8 +13,6 @@ export function StoredScenesList({
|
|||||||
currentId,
|
currentId,
|
||||||
onChange,
|
onChange,
|
||||||
}: StoredScenesListProps) {
|
}: StoredScenesListProps) {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<select
|
<select
|
||||||
|
92
src/i18n.ts
92
src/i18n.ts
@ -1,36 +1,68 @@
|
|||||||
import i18n from "i18next";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
|
|
||||||
import Backend from "i18next-xhr-backend";
|
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
|
||||||
export const fallbackLng = "en";
|
|
||||||
|
|
||||||
export function parseDetectedLang(lng: string | undefined): string {
|
|
||||||
if (lng) {
|
|
||||||
const [lang] = i18n.language.split("-");
|
|
||||||
return lang;
|
|
||||||
}
|
|
||||||
return fallbackLng;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const languages = [
|
export const languages = [
|
||||||
{ lng: "de", label: "Deutsch" },
|
{ lng: "en", label: "English", data: require("./locales/en.json") },
|
||||||
{ lng: "en", label: "English" },
|
{ lng: "de", label: "Deutsch", data: require("./locales/de.json") },
|
||||||
{ lng: "es", label: "Español" },
|
{ lng: "es", label: "Español", data: require("./locales/es.json") },
|
||||||
{ lng: "fr", label: "Français" },
|
{ lng: "fr", label: "Français", data: require("./locales/fr.json") },
|
||||||
{ lng: "pt", label: "Português" },
|
{ lng: "pt", label: "Português", data: require("./locales/pt.json") },
|
||||||
{ lng: "ru", label: "Русский" },
|
{ lng: "ru", label: "Русский", data: require("./locales/ru.json") },
|
||||||
];
|
];
|
||||||
|
|
||||||
i18n
|
let currentLanguage = languages[0];
|
||||||
.use(Backend)
|
const fallbackLanguage = languages[0];
|
||||||
.use(LanguageDetector)
|
|
||||||
.use(initReactI18next)
|
|
||||||
.init({
|
|
||||||
fallbackLng,
|
|
||||||
react: { useSuspense: false },
|
|
||||||
load: "languageOnly",
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
export function setLanguage(newLng: string | undefined) {
|
||||||
|
currentLanguage =
|
||||||
|
languages.find(language => language.lng === newLng) || fallbackLanguage;
|
||||||
|
|
||||||
|
languageDetector.cacheUserLanguage(currentLanguage.lng);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLanguage() {
|
||||||
|
return currentLanguage.lng;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPartsForData(data: any, parts: string[]) {
|
||||||
|
for (var i = 0; i < parts.length; ++i) {
|
||||||
|
const part = parts[i];
|
||||||
|
if (data[part] === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
data = data[part];
|
||||||
|
}
|
||||||
|
if (typeof data !== "string") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function t(path: string, replacement?: { [key: string]: string }) {
|
||||||
|
const parts = path.split(".");
|
||||||
|
let translation =
|
||||||
|
findPartsForData(currentLanguage.data, parts) ||
|
||||||
|
findPartsForData(fallbackLanguage.data, parts);
|
||||||
|
if (translation === undefined) {
|
||||||
|
throw new Error("Can't find translation for " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replacement) {
|
||||||
|
for (var key in replacement) {
|
||||||
|
translation = translation.replace("{{" + key + "}}", replacement[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const languageDetector = new LanguageDetector();
|
||||||
|
languageDetector.init({
|
||||||
|
languageUtils: {
|
||||||
|
formatLanguageCode: function(lng: string) {
|
||||||
|
return lng;
|
||||||
|
},
|
||||||
|
isWhitelisted: () => true,
|
||||||
|
},
|
||||||
|
checkWhitelist: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
setLanguage(languageDetector.detect());
|
||||||
|
@ -85,9 +85,8 @@ import { FixedSideContainer } from "./components/FixedSideContainer";
|
|||||||
import { ToolButton } from "./components/ToolButton";
|
import { ToolButton } from "./components/ToolButton";
|
||||||
import { LockIcon } from "./components/LockIcon";
|
import { LockIcon } from "./components/LockIcon";
|
||||||
import { ExportDialog } from "./components/ExportDialog";
|
import { ExportDialog } from "./components/ExportDialog";
|
||||||
import { withTranslation } from "react-i18next";
|
|
||||||
import { LanguageList } from "./components/LanguageList";
|
import { LanguageList } from "./components/LanguageList";
|
||||||
import i18n, { languages, parseDetectedLang } from "./i18n";
|
import { t, languages, setLanguage, getLanguage } from "./i18n";
|
||||||
import { StoredScenesList } from "./components/StoredScenesList";
|
import { StoredScenesList } from "./components/StoredScenesList";
|
||||||
|
|
||||||
let { elements } = createScene();
|
let { elements } = createScene();
|
||||||
@ -448,7 +447,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private renderSelectedShapeActions(elements: readonly ExcalidrawElement[]) {
|
private renderSelectedShapeActions(elements: readonly ExcalidrawElement[]) {
|
||||||
const { t } = this.props;
|
|
||||||
const { elementType, editingElement } = this.state;
|
const { elementType, editingElement } = this.state;
|
||||||
const targetElements = editingElement
|
const targetElements = editingElement
|
||||||
? [editingElement]
|
? [editingElement]
|
||||||
@ -465,7 +463,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
{(hasBackground(elementType) ||
|
{(hasBackground(elementType) ||
|
||||||
targetElements.some(element => hasBackground(element.type))) && (
|
targetElements.some(element => hasBackground(element.type))) && (
|
||||||
@ -475,7 +472,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -483,7 +479,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -496,7 +491,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -504,7 +498,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -517,7 +510,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -525,7 +517,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -535,7 +526,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -543,7 +533,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Island>
|
</Island>
|
||||||
@ -551,8 +540,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderShapesSwitcher() {
|
private renderShapesSwitcher() {
|
||||||
const { t } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{SHAPES.map(({ value, icon }, index) => {
|
{SHAPES.map(({ value, icon }, index) => {
|
||||||
@ -584,7 +571,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderCanvasActions() {
|
private renderCanvasActions() {
|
||||||
const { t } = this.props;
|
|
||||||
return (
|
return (
|
||||||
<Stack.Col gap={4}>
|
<Stack.Col gap={4}>
|
||||||
<Stack.Row justifyContent={"space-between"}>
|
<Stack.Row justifyContent={"space-between"}>
|
||||||
@ -593,14 +579,12 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
"saveScene",
|
"saveScene",
|
||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
<ExportDialog
|
<ExportDialog
|
||||||
elements={elements}
|
elements={elements}
|
||||||
@ -653,7 +637,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -661,7 +644,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t,
|
|
||||||
)}
|
)}
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
);
|
);
|
||||||
@ -670,7 +652,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
public render() {
|
public render() {
|
||||||
const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT;
|
const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT;
|
||||||
const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP;
|
const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP;
|
||||||
const { t } = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container">
|
||||||
@ -779,7 +760,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
action => this.canvasOnlyActions.includes(action),
|
action => this.canvasOnlyActions.includes(action),
|
||||||
t,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
top: e.clientY,
|
top: e.clientY,
|
||||||
@ -809,7 +789,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
action => !this.canvasOnlyActions.includes(action),
|
action => !this.canvasOnlyActions.includes(action),
|
||||||
t,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
top: e.clientY,
|
top: e.clientY,
|
||||||
@ -1480,11 +1459,12 @@ export class App extends React.Component<any, AppState> {
|
|||||||
</main>
|
</main>
|
||||||
<footer role="contentinfo">
|
<footer role="contentinfo">
|
||||||
<LanguageList
|
<LanguageList
|
||||||
onClick={lng => {
|
onChange={lng => {
|
||||||
i18n.changeLanguage(lng);
|
setLanguage(lng);
|
||||||
|
this.setState({});
|
||||||
}}
|
}}
|
||||||
languages={languages}
|
languages={languages}
|
||||||
currentLanguage={parseDetectedLang(i18n.language)}
|
currentLanguage={getLanguage()}
|
||||||
/>
|
/>
|
||||||
{this.renderIdsDropdown()}
|
{this.renderIdsDropdown()}
|
||||||
</footer>
|
</footer>
|
||||||
@ -1614,8 +1594,6 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AppWithTrans = withTranslation()(App);
|
|
||||||
|
|
||||||
const rootElement = document.getElementById("root");
|
const rootElement = document.getElementById("root");
|
||||||
|
|
||||||
class TopErrorBoundary extends React.Component {
|
class TopErrorBoundary extends React.Component {
|
||||||
@ -1710,7 +1688,7 @@ class TopErrorBoundary extends React.Component {
|
|||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<TopErrorBoundary>
|
<TopErrorBoundary>
|
||||||
<AppWithTrans />
|
<App />
|
||||||
</TopErrorBoundary>,
|
</TopErrorBoundary>,
|
||||||
rootElement,
|
rootElement,
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ import nanoid from "nanoid";
|
|||||||
import { fileOpen, fileSave } from "browser-nativefs";
|
import { fileOpen, fileSave } from "browser-nativefs";
|
||||||
import { getCommonBounds } from "../element";
|
import { getCommonBounds } from "../element";
|
||||||
|
|
||||||
import i18n from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = "excalidraw";
|
const LOCAL_STORAGE_KEY = "excalidraw";
|
||||||
const LOCAL_STORAGE_SCENE_PREVIOUS_KEY = "excalidraw-previos-scenes";
|
const LOCAL_STORAGE_SCENE_PREVIOUS_KEY = "excalidraw-previos-scenes";
|
||||||
@ -142,16 +142,15 @@ export async function exportToBackend(
|
|||||||
|
|
||||||
await navigator.clipboard.writeText(url.toString());
|
await navigator.clipboard.writeText(url.toString());
|
||||||
window.alert(
|
window.alert(
|
||||||
i18n.t("alerts.copiedToClipboard", {
|
t("alerts.copiedToClipboard", {
|
||||||
url: url.toString(),
|
url: url.toString(),
|
||||||
interpolation: { escapeValue: false },
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
window.alert(i18n.t("alerts.couldNotCreateShareableLink"));
|
window.alert(t("alerts.couldNotCreateShareableLink"));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
window.alert(i18n.t("alerts.couldNotCreateShareableLink"));
|
window.alert(t("alerts.couldNotCreateShareableLink"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +166,7 @@ export async function importFromBackend(id: string | null) {
|
|||||||
elements = response.elements || elements;
|
elements = response.elements || elements;
|
||||||
appState = response.appState || appState;
|
appState = response.appState || appState;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.alert(i18n.t("alerts.importBackendFailed"));
|
window.alert(t("alerts.importBackendFailed"));
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +192,7 @@ export async function exportCanvas(
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (!elements.length)
|
if (!elements.length)
|
||||||
return window.alert(i18n.t("alerts.cannotExportEmptyCanvas"));
|
return window.alert(t("alerts.cannotExportEmptyCanvas"));
|
||||||
// calculate smallest area to fit the contents in
|
// calculate smallest area to fit the contents in
|
||||||
|
|
||||||
if (type === "svg") {
|
if (type === "svg") {
|
||||||
@ -227,7 +226,7 @@ export async function exportCanvas(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (type === "clipboard") {
|
} else if (type === "clipboard") {
|
||||||
const errorMsg = i18n.t("alerts.couldNotCopyToClipboard");
|
const errorMsg = t("alerts.couldNotCopyToClipboard");
|
||||||
try {
|
try {
|
||||||
tempCanvas.toBlob(async function(blob: any) {
|
tempCanvas.toBlob(async function(blob: any) {
|
||||||
try {
|
try {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user