mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Internationalization support (#477)
* add i18next lib add some translations * add translations * fix font-family * fix pin versions
This commit is contained in:
parent
1a03a29025
commit
ff7a340d2f
15 changed files with 286 additions and 162 deletions
|
@ -31,14 +31,14 @@ export const actionClearCanvas: Action = {
|
|||
appState: getDefaultAppState()
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
PanelComponent: ({ updateData, t }) => (
|
||||
<ToolIcon
|
||||
type="button"
|
||||
icon={trash}
|
||||
title="Clear the canvas & reset background color"
|
||||
aria-label="Clear the canvas & reset background color"
|
||||
title={t("buttons.clearReset")}
|
||||
aria-label={t("buttons.clearReset")}
|
||||
onClick={() => {
|
||||
if (window.confirm("This will clear the whole canvas. Are you sure?")) {
|
||||
if (window.confirm(t("alerts.clearReset"))) {
|
||||
// TODO: Defined globally, since file handles aren't yet serializable.
|
||||
// Once `FileSystemFileHandle` can be serialized, make this
|
||||
// part of `AppState`.
|
||||
|
|
|
@ -9,7 +9,7 @@ export const actionDeleteSelected: Action = {
|
|||
elements: deleteSelectedElements(elements)
|
||||
};
|
||||
},
|
||||
contextItemLabel: "Delete",
|
||||
contextItemLabel: "labels.delete",
|
||||
contextMenuOrder: 3,
|
||||
keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ export const actionChangeExportBackground: Action = {
|
|||
perform: (elements, appState, value) => {
|
||||
return { appState: { ...appState, exportBackground: value } };
|
||||
},
|
||||
PanelComponent: ({ appState, updateData }) => (
|
||||
PanelComponent: ({ appState, updateData, t }) => (
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
@ -32,7 +32,7 @@ export const actionChangeExportBackground: Action = {
|
|||
updateData(e.target.checked);
|
||||
}}
|
||||
/>{" "}
|
||||
With background
|
||||
{t("labels.withBackground")}
|
||||
</label>
|
||||
)
|
||||
};
|
||||
|
@ -43,12 +43,12 @@ export const actionSaveScene: Action = {
|
|||
saveAsJSON(elements, appState).catch(err => console.error(err));
|
||||
return {};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
PanelComponent: ({ updateData, t }) => (
|
||||
<ToolIcon
|
||||
type="button"
|
||||
icon={save}
|
||||
title="Save"
|
||||
aria-label="Save"
|
||||
title={t("buttons.save")}
|
||||
aria-label={t("buttons.save")}
|
||||
onClick={() => updateData(null)}
|
||||
/>
|
||||
)
|
||||
|
@ -63,12 +63,12 @@ export const actionLoadScene: Action = {
|
|||
) => {
|
||||
return { elements: loadedElements, appState: loadedAppState };
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
PanelComponent: ({ updateData, t }) => (
|
||||
<ToolIcon
|
||||
type="button"
|
||||
icon={load}
|
||||
title="Load"
|
||||
aria-label="Load"
|
||||
title={t("buttons.load")}
|
||||
aria-label={t("buttons.load")}
|
||||
onClick={() => {
|
||||
loadFromJSON()
|
||||
.then(({ elements, appState }) => {
|
||||
|
|
|
@ -30,19 +30,21 @@ export const actionChangeStrokeColor: Action = {
|
|||
appState: { ...appState, currentItemStrokeColor: value }
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<>
|
||||
<h5>Stroke</h5>
|
||||
<ColorPicker
|
||||
type="elementStroke"
|
||||
color={
|
||||
getSelectedAttribute(elements, element => element.strokeColor) ||
|
||||
appState.currentItemStrokeColor
|
||||
}
|
||||
onChange={updateData}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
PanelComponent: ({ elements, appState, updateData, t }) => {
|
||||
return (
|
||||
<>
|
||||
<h5>{t("labels.stroke")}</h5>
|
||||
<ColorPicker
|
||||
type="elementStroke"
|
||||
color={
|
||||
getSelectedAttribute(elements, element => element.strokeColor) ||
|
||||
appState.currentItemStrokeColor
|
||||
}
|
||||
onChange={updateData}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const actionChangeBackgroundColor: Action = {
|
||||
|
@ -57,9 +59,9 @@ export const actionChangeBackgroundColor: Action = {
|
|||
appState: { ...appState, currentItemBackgroundColor: value }
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||
<>
|
||||
<h5>Background</h5>
|
||||
<h5>{t("labels.background")}</h5>
|
||||
<ColorPicker
|
||||
type="elementBackground"
|
||||
color={
|
||||
|
@ -83,9 +85,9 @@ export const actionChangeFillStyle: Action = {
|
|||
}))
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, updateData }) => (
|
||||
PanelComponent: ({ elements, updateData, t }) => (
|
||||
<>
|
||||
<h5>Fill</h5>
|
||||
<h5>{t("labels.fill")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: "solid", text: "Solid" },
|
||||
|
@ -112,9 +114,9 @@ export const actionChangeStrokeWidth: Action = {
|
|||
}))
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||
<>
|
||||
<h5>Stroke Width</h5>
|
||||
<h5>{t("labels.strokeWidth")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: 1, text: "Thin" },
|
||||
|
@ -139,9 +141,9 @@ export const actionChangeSloppiness: Action = {
|
|||
}))
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||
<>
|
||||
<h5>Sloppiness</h5>
|
||||
<h5>{t("labels.sloppiness")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: 0, text: "Architect" },
|
||||
|
@ -166,9 +168,9 @@ export const actionChangeOpacity: Action = {
|
|||
}))
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, updateData }) => (
|
||||
PanelComponent: ({ elements, updateData, t }) => (
|
||||
<>
|
||||
<h5>Opacity</h5>
|
||||
<h5>{t("labels.oppacity")}</h5>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
|
@ -202,9 +204,9 @@ export const actionChangeFontSize: Action = {
|
|||
})
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, updateData }) => (
|
||||
PanelComponent: ({ elements, updateData, t }) => (
|
||||
<>
|
||||
<h5>Font size</h5>
|
||||
<h5>{t("labels.fontSize")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: 16, text: "Small" },
|
||||
|
@ -241,14 +243,14 @@ export const actionChangeFontFamily: Action = {
|
|||
})
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ elements, updateData }) => (
|
||||
PanelComponent: ({ elements, updateData, t }) => (
|
||||
<>
|
||||
<h5>Font family</h5>
|
||||
<h5>{t("labels.fontFamily")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: "Virgil", text: "Hand-drawn" },
|
||||
{ value: "Helvetica", text: "Normal" },
|
||||
{ value: "Cascadia", text: "Code" }
|
||||
{ value: "Virgil", text: t("labels.handDrawn") },
|
||||
{ value: "Helvetica", text: t("labels.normal") },
|
||||
{ value: "Cascadia", text: t("labels.code") }
|
||||
]}
|
||||
value={getSelectedAttribute(
|
||||
elements,
|
||||
|
|
|
@ -8,6 +8,6 @@ export const actionSelectAll: Action = {
|
|||
elements: elements.map(elem => ({ ...elem, isSelected: true }))
|
||||
};
|
||||
},
|
||||
contextItemLabel: "Select All",
|
||||
contextItemLabel: "labels.selectAll",
|
||||
keyTest: event => event[KEYS.META] && event.code === "KeyA"
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ export const actionCopyStyles: Action = {
|
|||
}
|
||||
return {};
|
||||
},
|
||||
contextItemLabel: "Copy Styles",
|
||||
contextItemLabel: "labels.copyStyles",
|
||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyC",
|
||||
contextMenuOrder: 0
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ export const actionPasteStyles: Action = {
|
|||
})
|
||||
};
|
||||
},
|
||||
contextItemLabel: "Paste Styles",
|
||||
contextItemLabel: "labels.pasteStyles",
|
||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyV",
|
||||
contextMenuOrder: 1
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ export const actionSendBackward: Action = {
|
|||
appState
|
||||
};
|
||||
},
|
||||
contextItemLabel: "Send Backward",
|
||||
contextItemLabel: "labels.sendBackward",
|
||||
keyPriority: 40,
|
||||
keyTest: event =>
|
||||
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyB"
|
||||
|
@ -30,7 +30,7 @@ export const actionBringForward: Action = {
|
|||
appState
|
||||
};
|
||||
},
|
||||
contextItemLabel: "Bring Forward",
|
||||
contextItemLabel: "labels.bringForward",
|
||||
keyPriority: 40,
|
||||
keyTest: event =>
|
||||
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyF"
|
||||
|
@ -44,7 +44,7 @@ export const actionSendToBack: Action = {
|
|||
appState
|
||||
};
|
||||
},
|
||||
contextItemLabel: "Send to Back",
|
||||
contextItemLabel: "labels.sendToBack",
|
||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyB"
|
||||
};
|
||||
|
||||
|
@ -56,6 +56,6 @@ export const actionBringToFront: Action = {
|
|||
appState
|
||||
};
|
||||
},
|
||||
contextItemLabel: "Bring to Front",
|
||||
contextItemLabel: "labels.bringToFront",
|
||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyF"
|
||||
};
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from "./types";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
export class ActionManager implements ActionsManagerInterface {
|
||||
actions: { [keyProp: string]: Action } = {};
|
||||
|
@ -46,7 +47,8 @@ export class ActionManager implements ActionsManagerInterface {
|
|||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
updater: UpdaterFn,
|
||||
actionFilter: ActionFilterFn = action => action
|
||||
actionFilter: ActionFilterFn = action => action,
|
||||
t?: TFunction
|
||||
) {
|
||||
return Object.values(this.actions)
|
||||
.filter(actionFilter)
|
||||
|
@ -57,7 +59,10 @@ export class ActionManager implements ActionsManagerInterface {
|
|||
(b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999)
|
||||
)
|
||||
.map(action => ({
|
||||
label: action.contextItemLabel!,
|
||||
label:
|
||||
t && action.contextItemLabel
|
||||
? t(action.contextItemLabel)
|
||||
: action.contextItemLabel!,
|
||||
action: () => {
|
||||
updater(action.perform(elements, appState, null));
|
||||
}
|
||||
|
@ -68,7 +73,8 @@ export class ActionManager implements ActionsManagerInterface {
|
|||
name: string,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
updater: UpdaterFn
|
||||
updater: UpdaterFn,
|
||||
t: TFunction
|
||||
) {
|
||||
if (this.actions[name] && "PanelComponent" in this.actions[name]) {
|
||||
const action = this.actions[name];
|
||||
|
@ -82,6 +88,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||
elements={elements}
|
||||
appState={appState}
|
||||
updateData={updateData}
|
||||
t={t}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState } from "../types";
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
export type ActionResult = {
|
||||
elements?: ExcalidrawElement[];
|
||||
|
@ -22,6 +23,7 @@ export interface Action {
|
|||
elements: readonly ExcalidrawElement[];
|
||||
appState: AppState;
|
||||
updateData: (formData: any) => void;
|
||||
t: TFunction;
|
||||
}>;
|
||||
perform: ActionFn;
|
||||
keyPriority?: number;
|
||||
|
@ -54,6 +56,7 @@ export interface ActionsManagerInterface {
|
|||
name: string,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
updater: UpdaterFn
|
||||
updater: UpdaterFn,
|
||||
t: TFunction
|
||||
) => React.ReactElement | null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue