Internationalization followup (#500)
* add translations in data.ts * add language list add spanish version * fixes pr review * add more translations * remove unused label Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
parent
362cd74a9b
commit
a436e70764
@ -22,7 +22,20 @@
|
||||
"withBackground": "With Background",
|
||||
"handDrawn": "Hand-Drawn",
|
||||
"normal": "Normal",
|
||||
"code": "Code"
|
||||
"code": "Code",
|
||||
"small": "Small",
|
||||
"medium": "Medium",
|
||||
"large": "Large",
|
||||
"veryLarge": "Very Large",
|
||||
"solid": "Solid",
|
||||
"hachure": "Hachure",
|
||||
"crossHatch": "Cross-Hatch",
|
||||
"thin": "Thin",
|
||||
"bold": "Bold",
|
||||
"extraBold": "Extra Bold",
|
||||
"architect": "Architect",
|
||||
"artist": "Artist",
|
||||
"cartoonist": "Cartoonist"
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Clear the canvas & reset background color",
|
||||
@ -30,10 +43,16 @@
|
||||
"exportToPng": "Export to PNG",
|
||||
"copyToClipboard": "Copy to clipboard",
|
||||
"save": "Save",
|
||||
"load": "Load"
|
||||
"load": "Load",
|
||||
"getShareableLink": "Get shareable link"
|
||||
},
|
||||
"alerts": {
|
||||
"clearReset": "This will clear the whole canvas. Are you sure?"
|
||||
"clearReset": "This will clear the whole canvas. Are you sure?",
|
||||
"couldNotCreateShareableLink": "Couldn't create shareable link.",
|
||||
"importBackendFailed": "Importing from backend failed.",
|
||||
"cannotExportEmptyCanvas": "Cannot export empty canvas.",
|
||||
"couldNotCopyToClipboard": "Couldn't copy to clipboard. Try using Chrome browser.",
|
||||
"copiedToClipboard": "Copied to clipboard: {{url}}"
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selection",
|
||||
|
66
public/locales/es/translation.json
Normal file
66
public/locales/es/translation.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"labels": {
|
||||
"paste": "Pegar",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"copy": "Copiar",
|
||||
"bringForward": "Adelantar",
|
||||
"sendToBack": "Send To Back",
|
||||
"bringToFront": "Traer al frente",
|
||||
"sendBackward": "Enviar átras",
|
||||
"delete": "Borrar",
|
||||
"copyStyles": "Copiar estilos",
|
||||
"pasteStyles": "Pegar estilos",
|
||||
"stroke": "Trazo",
|
||||
"background": "Fondo",
|
||||
"fill": "Rellenar",
|
||||
"strokeWidth": "Ancho de trazo",
|
||||
"sloppiness": "Estilo de trazo",
|
||||
"opacity": "Opacidad",
|
||||
"fontSize": "Tamaño de letra",
|
||||
"fontFamily": "Tipo de letra",
|
||||
"onlySelected": "Sólo seleccionados",
|
||||
"withBackground": "Con fondo",
|
||||
"handDrawn": "Dibujo a Mano",
|
||||
"normal": "Normal",
|
||||
"code": "Código",
|
||||
"small": "Pequeña",
|
||||
"medium": "Mediana",
|
||||
"large": "Grande",
|
||||
"veryLarge": "Muy Grande",
|
||||
"solid": "Sólido",
|
||||
"hachure": "Folleto",
|
||||
"crossHatch": "Rayado transversal",
|
||||
"thin": "Fino",
|
||||
"bold": "Grueso",
|
||||
"extraBold": "Extra Grueso",
|
||||
"architect": "Arquitecto",
|
||||
"artist": "Artista",
|
||||
"cartoonist": "Caricatura"
|
||||
},
|
||||
"buttons": {
|
||||
"clearReset": "Limpiar lienzo y reiniciar el color de fondo",
|
||||
"export": "Exportar",
|
||||
"exportToPng": "Exportar a PNG",
|
||||
"copyToClipboard": "Copiar al portapapeles",
|
||||
"save": "Guardar",
|
||||
"load": "Cargar",
|
||||
"getShareableLink": "Obtener enlace para compartir"
|
||||
},
|
||||
"alerts": {
|
||||
"clearReset": "Esto limpiará todo el lienzo. Estás seguro?",
|
||||
"couldNotCreateShareableLink": "No se pudo crear un enlace para compartir.",
|
||||
"importBackendFailed": "La importación falló.",
|
||||
"cannotExportEmptyCanvas": "No se puede exportar un lienzo vació",
|
||||
"couldNotCopyToClipboard": "No se ha podido copiar al portapapeles, intente usar Chrome como navegador.",
|
||||
"copiedToClipboard": "Copiado en el portapapeles: {{url}}"
|
||||
},
|
||||
"toolBar": {
|
||||
"selection": "Selección",
|
||||
"rectangle": "Rectángulo",
|
||||
"diamond": "Diamante",
|
||||
"ellipse": "Elipse",
|
||||
"arrow": "Flecha",
|
||||
"line": "Línea",
|
||||
"text": "Texto"
|
||||
}
|
||||
}
|
@ -107,9 +107,9 @@ export const actionChangeFillStyle: Action = {
|
||||
<h5>{t("labels.fill")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: "solid", text: "Solid" },
|
||||
{ value: "hachure", text: "Hachure" },
|
||||
{ value: "cross-hatch", text: "Cross-hatch" }
|
||||
{ value: "solid", text: t("labels.solid") },
|
||||
{ value: "hachure", text: t("labels.hachure") },
|
||||
{ value: "cross-hatch", text: t("labels.crossHatch") }
|
||||
]}
|
||||
value={getFormValue(
|
||||
appState.editingElement,
|
||||
@ -140,9 +140,9 @@ export const actionChangeStrokeWidth: Action = {
|
||||
<h5>{t("labels.strokeWidth")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: 1, text: "Thin" },
|
||||
{ value: 2, text: "Bold" },
|
||||
{ value: 4, text: "Extra Bold" }
|
||||
{ value: 1, text: t("labels.thin") },
|
||||
{ value: 2, text: t("labels.bold") },
|
||||
{ value: 4, text: t("labels.extraBold") }
|
||||
]}
|
||||
value={getFormValue(
|
||||
appState.editingElement,
|
||||
@ -171,9 +171,9 @@ export const actionChangeSloppiness: Action = {
|
||||
<h5>{t("labels.sloppiness")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: 0, text: "Architect" },
|
||||
{ value: 1, text: "Artist" },
|
||||
{ value: 3, text: "Cartoonist" }
|
||||
{ value: 0, text: t("labels.architect") },
|
||||
{ value: 1, text: t("labels.artist") },
|
||||
{ value: 3, text: t("labels.cartoonist") }
|
||||
]}
|
||||
value={getFormValue(
|
||||
appState.editingElement,
|
||||
@ -242,10 +242,10 @@ export const actionChangeFontSize: Action = {
|
||||
<h5>{t("labels.fontSize")}</h5>
|
||||
<ButtonSelect
|
||||
options={[
|
||||
{ value: 16, text: "Small" },
|
||||
{ value: 20, text: "Medium" },
|
||||
{ value: 28, text: "Large" },
|
||||
{ value: 36, text: "Very Large" }
|
||||
{ value: 16, text: t("labels.small") },
|
||||
{ value: 20, text: t("labels.medium") },
|
||||
{ value: 28, text: t("labels.large") },
|
||||
{ value: 36, text: t("labels.veryLarge") }
|
||||
]}
|
||||
value={getFormValue(
|
||||
appState.editingElement,
|
||||
|
@ -127,8 +127,8 @@ export function ExportDialog({
|
||||
<ToolIcon
|
||||
type="button"
|
||||
icon={link}
|
||||
title="Get shareable link"
|
||||
aria-label="Get shareable link"
|
||||
title={t("buttons.getShareableLink")}
|
||||
aria-label={t("buttons.getShareableLink")}
|
||||
onClick={() => onExportToBackend(exportedElements, 1)}
|
||||
/>
|
||||
</Stack.Row>
|
||||
|
32
src/components/LanguageList.tsx
Normal file
32
src/components/LanguageList.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
export function LanguageList<T>({
|
||||
onClick,
|
||||
languages,
|
||||
currentLanguage
|
||||
}: {
|
||||
languages: { lng: string; label: string }[];
|
||||
onClick: (value: string) => void;
|
||||
currentLanguage: string;
|
||||
}) {
|
||||
return (
|
||||
<ul>
|
||||
{languages.map((language, idx) => (
|
||||
<li
|
||||
key={idx}
|
||||
className={currentLanguage === language.lng ? "current" : ""}
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
onClick={e => {
|
||||
onClick(language.lng);
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{language.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
25
src/i18n.ts
25
src/i18n.ts
@ -4,18 +4,29 @@ import { initReactI18next } from "react-i18next";
|
||||
import Backend from "i18next-xhr-backend";
|
||||
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 = [
|
||||
{ lng: "en", label: "English" },
|
||||
{ lng: "es", label: "Español" }
|
||||
];
|
||||
|
||||
i18n
|
||||
.use(Backend)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
backend: {
|
||||
loadPath: "./locales/{{lng}}/translation.json"
|
||||
},
|
||||
lng: "en",
|
||||
fallbackLng: "en",
|
||||
debug: false,
|
||||
react: { useSuspense: false }
|
||||
fallbackLng,
|
||||
react: { useSuspense: false },
|
||||
load: "languageOnly"
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
@ -80,7 +80,8 @@ import { ToolIcon } from "./components/ToolIcon";
|
||||
import { LockIcon } from "./components/LockIcon";
|
||||
import { ExportDialog } from "./components/ExportDialog";
|
||||
import { withTranslation } from "react-i18next";
|
||||
import "./i18n";
|
||||
import { LanguageList } from "./components/LanguageList";
|
||||
import i18n, { languages, parseDetectedLang } from "./i18n";
|
||||
|
||||
let { elements } = createScene();
|
||||
const { history } = createHistory();
|
||||
@ -1261,6 +1262,15 @@ export class App extends React.Component<any, AppState> {
|
||||
document.documentElement.style.cursor = hitElement ? "move" : "";
|
||||
}}
|
||||
/>
|
||||
<div className="langBox">
|
||||
<LanguageList
|
||||
onClick={lng => {
|
||||
i18n.changeLanguage(lng);
|
||||
}}
|
||||
languages={languages}
|
||||
currentLanguage={parseDetectedLang(i18n.language)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ import { getExportCanvasPreview } from "./getExportCanvasPreview";
|
||||
import nanoid from "nanoid";
|
||||
import { fileOpenPromise, fileSavePromise } from "browser-nativefs";
|
||||
|
||||
import i18n from "../i18n";
|
||||
|
||||
const LOCAL_STORAGE_KEY = "excalidraw";
|
||||
const LOCAL_STORAGE_KEY_STATE = "excalidraw-state";
|
||||
const BACKEND_POST = "https://json.excalidraw.com/api/v1/post/";
|
||||
@ -120,9 +122,14 @@ export async function exportToBackend(
|
||||
url.searchParams.append("id", json.id);
|
||||
|
||||
await navigator.clipboard.writeText(url.toString());
|
||||
window.alert(`Copied to clipboard: ${url.toString()}`);
|
||||
window.alert(
|
||||
i18n.t("alerts.copiedToClipboard", {
|
||||
url: url.toString(),
|
||||
interpolation: { escapeValue: false }
|
||||
})
|
||||
);
|
||||
} else {
|
||||
window.alert("Couldn't create shareable link");
|
||||
window.alert(i18n.t("alerts.couldNotCreateShareableLink"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +144,7 @@ export async function importFromBackend(id: string | null) {
|
||||
elements = response.elements || elements;
|
||||
appState = response.appState || appState;
|
||||
} catch (error) {
|
||||
window.alert("Importing from backend failed");
|
||||
window.alert(i18n.t("alerts.importBackendFailed"));
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
@ -162,7 +169,8 @@ export async function exportCanvas(
|
||||
scale?: number;
|
||||
}
|
||||
) {
|
||||
if (!elements.length) return window.alert("Cannot export empty canvas.");
|
||||
if (!elements.length)
|
||||
return window.alert(i18n.t("alerts.cannotExportEmptyCanvas"));
|
||||
// calculate smallest area to fit the contents in
|
||||
|
||||
const tempCanvas = getExportCanvasPreview(elements, {
|
||||
@ -185,6 +193,7 @@ export async function exportCanvas(
|
||||
}
|
||||
});
|
||||
} else if (type === "clipboard") {
|
||||
const errorMsg = i18n.t("alerts.couldNotCopyToClipboard");
|
||||
try {
|
||||
tempCanvas.toBlob(async function(blob: any) {
|
||||
try {
|
||||
@ -192,11 +201,11 @@ export async function exportCanvas(
|
||||
new window.ClipboardItem({ "image/png": blob })
|
||||
]);
|
||||
} catch (err) {
|
||||
window.alert("Couldn't copy to clipboard. Try using Chrome browser.");
|
||||
window.alert(errorMsg);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
window.alert("Couldn't copy to clipboard. Try using Chrome browser.");
|
||||
window.alert(errorMsg);
|
||||
}
|
||||
} else if (type === "backend") {
|
||||
const appState = getDefaultAppState();
|
||||
|
@ -183,3 +183,30 @@ button {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.langBox {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin-right: 0.5em;
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul > li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
}
|
||||
li > a,
|
||||
li > a:visited {
|
||||
text-decoration: none;
|
||||
color: gray;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
li.current > a,
|
||||
li.current > a:visited {
|
||||
color: black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user