mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-10 11:35:52 +01:00
refactor: add typeScript support to enforce valid translation keys (#6776)
This commit is contained in:
parent
5e3550fc14
commit
f7c3644342
@ -65,7 +65,7 @@ export const actionChangeExportScale = register({
|
||||
);
|
||||
|
||||
const scaleButtonTitle = `${t(
|
||||
"buttons.scale",
|
||||
"imageExportDialog.label.scale",
|
||||
)} ${s}x (${width}x${height})`;
|
||||
|
||||
return (
|
||||
@ -102,7 +102,7 @@ export const actionChangeExportBackground = register({
|
||||
checked={appState.exportBackground}
|
||||
onChange={(checked) => updateData(checked)}
|
||||
>
|
||||
{t("labels.withBackground")}
|
||||
{t("imageExportDialog.label.withBackground")}
|
||||
</CheckboxItem>
|
||||
),
|
||||
});
|
||||
@ -121,8 +121,8 @@ export const actionChangeExportEmbedScene = register({
|
||||
checked={appState.exportEmbedScene}
|
||||
onChange={(checked) => updateData(checked)}
|
||||
>
|
||||
{t("labels.exportEmbedScene")}
|
||||
<Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
|
||||
{t("imageExportDialog.label.embedScene")}
|
||||
<Tooltip label={t("imageExportDialog.tooltip.embedScene")} long={true}>
|
||||
<div className="excalidraw-tooltip-icon">{questionCircle}</div>
|
||||
</Tooltip>
|
||||
</CheckboxItem>
|
||||
@ -277,7 +277,7 @@ export const actionExportWithDarkMode = register({
|
||||
onChange={(theme: Theme) => {
|
||||
updateData(theme === THEME.DARK);
|
||||
}}
|
||||
title={t("labels.toggleExportColorScheme")}
|
||||
title={t("imageExportDialog.label.darkMode")}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
} from "./colorPickerUtils";
|
||||
import HotkeyLabel from "./HotkeyLabel";
|
||||
import { ColorPaletteCustom } from "../../colors";
|
||||
import { t } from "../../i18n";
|
||||
import { TranslationKeys, t } from "../../i18n";
|
||||
|
||||
interface PickerColorListProps {
|
||||
palette: ColorPaletteCustom;
|
||||
@ -48,7 +48,11 @@ const PickerColorList = ({
|
||||
(Array.isArray(value) ? value[activeShade] : value) || "transparent";
|
||||
|
||||
const keybinding = colorPickerHotkeyBindings[index];
|
||||
const label = t(`colors.${key.replace(/\d+/, "")}`, null, "");
|
||||
const label = t(
|
||||
`colors.${key.replace(/\d+/, "")}` as unknown as TranslationKeys,
|
||||
null,
|
||||
"",
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
|
@ -1,6 +1,6 @@
|
||||
import clsx from "clsx";
|
||||
import { Popover } from "./Popover";
|
||||
import { t } from "../i18n";
|
||||
import { t, TranslationKeys } from "../i18n";
|
||||
|
||||
import "./ContextMenu.scss";
|
||||
import {
|
||||
@ -83,10 +83,14 @@ export const ContextMenu = React.memo(
|
||||
if (item.contextItemLabel) {
|
||||
if (typeof item.contextItemLabel === "function") {
|
||||
label = t(
|
||||
item.contextItemLabel(elements, appState, actionManager.app),
|
||||
item.contextItemLabel(
|
||||
elements,
|
||||
appState,
|
||||
actionManager.app,
|
||||
) as unknown as TranslationKeys,
|
||||
);
|
||||
} else {
|
||||
label = t(item.contextItemLabel);
|
||||
label = t(item.contextItemLabel as unknown as TranslationKeys);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { t } from "../i18n";
|
||||
import { useExcalidrawContainer } from "./App";
|
||||
|
||||
export const Section: React.FC<{
|
||||
heading: string;
|
||||
heading: "canvasActions" | "selectedShapeActions" | "shapes";
|
||||
children?: React.ReactNode | ((heading: React.ReactNode) => React.ReactNode);
|
||||
className?: string;
|
||||
}> = ({ heading, children, ...props }) => {
|
||||
|
@ -3,6 +3,7 @@ import { render } from "@testing-library/react";
|
||||
import fallbackLangData from "../locales/en.json";
|
||||
|
||||
import Trans from "./Trans";
|
||||
import { TranslationKeys } from "../i18n";
|
||||
|
||||
describe("Test <Trans/>", () => {
|
||||
it("should translate the the strings correctly", () => {
|
||||
@ -18,24 +19,27 @@ describe("Test <Trans/>", () => {
|
||||
const { getByTestId } = render(
|
||||
<>
|
||||
<div data-testid="test1">
|
||||
<Trans i18nKey="transTest.key1" audience="world" />
|
||||
<Trans
|
||||
i18nKey={"transTest.key1" as unknown as TranslationKeys}
|
||||
audience="world"
|
||||
/>
|
||||
</div>
|
||||
<div data-testid="test2">
|
||||
<Trans
|
||||
i18nKey="transTest.key2"
|
||||
i18nKey={"transTest.key2" as unknown as TranslationKeys}
|
||||
link={(el) => <a href="https://example.com">{el}</a>}
|
||||
/>
|
||||
</div>
|
||||
<div data-testid="test3">
|
||||
<Trans
|
||||
i18nKey="transTest.key3"
|
||||
i18nKey={"transTest.key3" as unknown as TranslationKeys}
|
||||
link={(el) => <a href="https://example.com">{el}</a>}
|
||||
location="the button"
|
||||
/>
|
||||
</div>
|
||||
<div data-testid="test4">
|
||||
<Trans
|
||||
i18nKey="transTest.key4"
|
||||
i18nKey={"transTest.key4" as unknown as TranslationKeys}
|
||||
link={(el) => <a href="https://example.com">{el}</a>}
|
||||
location="the button"
|
||||
bold={(el) => <strong>{el}</strong>}
|
||||
@ -43,7 +47,7 @@ describe("Test <Trans/>", () => {
|
||||
</div>
|
||||
<div data-testid="test5">
|
||||
<Trans
|
||||
i18nKey="transTest.key5"
|
||||
i18nKey={"transTest.key5" as unknown as TranslationKeys}
|
||||
connect-link={(el) => <a href="https://example.com">{el}</a>}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
|
||||
import { useI18n } from "../i18n";
|
||||
import { TranslationKeys, useI18n } from "../i18n";
|
||||
|
||||
// Used for splitting i18nKey into tokens in Trans component
|
||||
// Example:
|
||||
@ -153,7 +153,7 @@ const Trans = ({
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
i18nKey: string;
|
||||
i18nKey: TranslationKeys;
|
||||
[key: string]: React.ReactNode | ((el: React.ReactNode) => React.ReactNode);
|
||||
}) => {
|
||||
const { t } = useI18n();
|
||||
|
@ -3,6 +3,7 @@ import percentages from "./locales/percentages.json";
|
||||
import { ENV } from "./constants";
|
||||
import { jotaiScope, jotaiStore } from "./jotai";
|
||||
import { atom, useAtomValue } from "jotai";
|
||||
import { NestedKeyOf } from "./utility-types";
|
||||
|
||||
const COMPLETION_THRESHOLD = 85;
|
||||
|
||||
@ -12,6 +13,8 @@ export interface Language {
|
||||
rtl?: boolean;
|
||||
}
|
||||
|
||||
export type TranslationKeys = NestedKeyOf<typeof fallbackLangData>;
|
||||
|
||||
export const defaultLang = { code: "en", label: "English" };
|
||||
|
||||
export const languages: Language[] = [
|
||||
@ -123,7 +126,7 @@ const findPartsForData = (data: any, parts: string[]) => {
|
||||
};
|
||||
|
||||
export const t = (
|
||||
path: string,
|
||||
path: NestedKeyOf<typeof fallbackLangData>,
|
||||
replacement?: { [key: string]: string | number } | null,
|
||||
fallback?: string,
|
||||
) => {
|
||||
|
@ -50,3 +50,7 @@ export type ExtractSetType<T extends Set<any>> = T extends Set<infer U>
|
||||
|
||||
export type SameType<T, U> = T extends U ? (U extends T ? true : false) : false;
|
||||
export type Assert<T extends true> = T;
|
||||
|
||||
export type NestedKeyOf<T, K = keyof T> = K extends keyof T & (string | number)
|
||||
? `${K}` | (T[K] extends object ? `${K}.${NestedKeyOf<T[K]>}` : never)
|
||||
: never;
|
||||
|
Loading…
Reference in New Issue
Block a user