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:
Fernando Alava Zambrano 2020-01-22 16:25:04 +02:00 committed by Lipis
parent 362cd74a9b
commit a436e70764
9 changed files with 206 additions and 32 deletions

View file

@ -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,

View file

@ -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>

View 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>
);
}

View file

@ -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;

View file

@ -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>
);
}

View file

@ -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();

View file

@ -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;
}
}