fix: rerender i18n in host components on lang change (#6224)
This commit is contained in:
parent
e4506be3e8
commit
04a8c22f39
@ -1,5 +1,5 @@
|
|||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { t } from "../../i18n";
|
import { useI18n } from "../../i18n";
|
||||||
import {
|
import {
|
||||||
useExcalidrawAppState,
|
useExcalidrawAppState,
|
||||||
useExcalidrawSetAppState,
|
useExcalidrawSetAppState,
|
||||||
@ -33,9 +33,7 @@ import { useSetAtom } from "jotai";
|
|||||||
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
import { activeConfirmDialogAtom } from "../ActiveConfirmDialog";
|
||||||
|
|
||||||
export const LoadScene = () => {
|
export const LoadScene = () => {
|
||||||
// FIXME Hack until we tie "t" to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (!actionManager.isActionEnabled(actionLoadScene)) {
|
if (!actionManager.isActionEnabled(actionLoadScene)) {
|
||||||
@ -57,9 +55,7 @@ export const LoadScene = () => {
|
|||||||
LoadScene.displayName = "LoadScene";
|
LoadScene.displayName = "LoadScene";
|
||||||
|
|
||||||
export const SaveToActiveFile = () => {
|
export const SaveToActiveFile = () => {
|
||||||
// FIXME Hack until we tie "t" to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
if (!actionManager.isActionEnabled(actionSaveToActiveFile)) {
|
if (!actionManager.isActionEnabled(actionSaveToActiveFile)) {
|
||||||
@ -80,9 +76,7 @@ SaveToActiveFile.displayName = "SaveToActiveFile";
|
|||||||
|
|
||||||
export const SaveAsImage = () => {
|
export const SaveAsImage = () => {
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
// FIXME Hack until we tie "t" to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
icon={ExportImageIcon}
|
icon={ExportImageIcon}
|
||||||
@ -98,9 +92,7 @@ export const SaveAsImage = () => {
|
|||||||
SaveAsImage.displayName = "SaveAsImage";
|
SaveAsImage.displayName = "SaveAsImage";
|
||||||
|
|
||||||
export const Help = () => {
|
export const Help = () => {
|
||||||
// FIXME Hack until we tie "t" to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
|
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
@ -119,9 +111,8 @@ export const Help = () => {
|
|||||||
Help.displayName = "Help";
|
Help.displayName = "Help";
|
||||||
|
|
||||||
export const ClearCanvas = () => {
|
export const ClearCanvas = () => {
|
||||||
// FIXME Hack until we tie "t" to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom);
|
const setActiveConfirmDialog = useSetAtom(activeConfirmDialogAtom);
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
@ -143,6 +134,7 @@ export const ClearCanvas = () => {
|
|||||||
ClearCanvas.displayName = "ClearCanvas";
|
ClearCanvas.displayName = "ClearCanvas";
|
||||||
|
|
||||||
export const ToggleTheme = () => {
|
export const ToggleTheme = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useExcalidrawAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
@ -175,6 +167,7 @@ export const ToggleTheme = () => {
|
|||||||
ToggleTheme.displayName = "ToggleTheme";
|
ToggleTheme.displayName = "ToggleTheme";
|
||||||
|
|
||||||
export const ChangeCanvasBackground = () => {
|
export const ChangeCanvasBackground = () => {
|
||||||
|
const { t } = useI18n();
|
||||||
const appState = useExcalidrawAppState();
|
const appState = useExcalidrawAppState();
|
||||||
const actionManager = useExcalidrawActionManager();
|
const actionManager = useExcalidrawActionManager();
|
||||||
|
|
||||||
@ -195,9 +188,7 @@ export const ChangeCanvasBackground = () => {
|
|||||||
ChangeCanvasBackground.displayName = "ChangeCanvasBackground";
|
ChangeCanvasBackground.displayName = "ChangeCanvasBackground";
|
||||||
|
|
||||||
export const Export = () => {
|
export const Export = () => {
|
||||||
// FIXME Hack until we tie "t" to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
const setAppState = useExcalidrawSetAppState();
|
const setAppState = useExcalidrawSetAppState();
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@ -248,9 +239,7 @@ export const LiveCollaborationTrigger = ({
|
|||||||
onSelect: () => void;
|
onSelect: () => void;
|
||||||
isCollaborating: boolean;
|
isCollaborating: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
// FIXME Hack until we tie "t" to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
data-testid="collab-button"
|
data-testid="collab-button"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { actionLoadScene, actionShortcuts } from "../../actions";
|
import { actionLoadScene, actionShortcuts } from "../../actions";
|
||||||
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
import { getShortcutFromShortcutName } from "../../actions/shortcuts";
|
||||||
import { t } from "../../i18n";
|
import { t, useI18n } from "../../i18n";
|
||||||
import {
|
import {
|
||||||
useDevice,
|
useDevice,
|
||||||
useExcalidrawActionManager,
|
useExcalidrawActionManager,
|
||||||
@ -172,10 +172,7 @@ const MenuItemLiveCollaborationTrigger = ({
|
|||||||
}: {
|
}: {
|
||||||
onSelect: () => any;
|
onSelect: () => any;
|
||||||
}) => {
|
}) => {
|
||||||
// FIXME when we tie t() to lang state
|
const { t } = useI18n();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const appState = useExcalidrawAppState();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WelcomeScreenMenuItem shortcut={null} onSelect={onSelect} icon={usersIcon}>
|
<WelcomeScreenMenuItem shortcut={null} onSelect={onSelect} icon={usersIcon}>
|
||||||
{t("labels.liveCollaboration")}
|
{t("labels.liveCollaboration")}
|
||||||
|
3
src/excalidraw-app/app-jotai.ts
Normal file
3
src/excalidraw-app/app-jotai.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { unstable_createStore } from "jotai";
|
||||||
|
|
||||||
|
export const appJotaiStore = unstable_createStore();
|
@ -70,7 +70,7 @@ import { decryptData } from "../../data/encryption";
|
|||||||
import { resetBrowserStateVersions } from "../data/tabSync";
|
import { resetBrowserStateVersions } from "../data/tabSync";
|
||||||
import { LocalData } from "../data/LocalData";
|
import { LocalData } from "../data/LocalData";
|
||||||
import { atom, useAtom } from "jotai";
|
import { atom, useAtom } from "jotai";
|
||||||
import { jotaiStore } from "../../jotai";
|
import { appJotaiStore } from "../app-jotai";
|
||||||
|
|
||||||
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
export const collabAPIAtom = atom<CollabAPI | null>(null);
|
||||||
export const collabDialogShownAtom = atom(false);
|
export const collabDialogShownAtom = atom(false);
|
||||||
@ -167,7 +167,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
setUsername: this.setUsername,
|
setUsername: this.setUsername,
|
||||||
};
|
};
|
||||||
|
|
||||||
jotaiStore.set(collabAPIAtom, collabAPI);
|
appJotaiStore.set(collabAPIAtom, collabAPI);
|
||||||
this.onOfflineStatusToggle();
|
this.onOfflineStatusToggle();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -185,7 +185,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onOfflineStatusToggle = () => {
|
onOfflineStatusToggle = () => {
|
||||||
jotaiStore.set(isOfflineAtom, !window.navigator.onLine);
|
appJotaiStore.set(isOfflineAtom, !window.navigator.onLine);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
@ -208,10 +208,10 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isCollaborating = () => jotaiStore.get(isCollaboratingAtom)!;
|
isCollaborating = () => appJotaiStore.get(isCollaboratingAtom)!;
|
||||||
|
|
||||||
private setIsCollaborating = (isCollaborating: boolean) => {
|
private setIsCollaborating = (isCollaborating: boolean) => {
|
||||||
jotaiStore.set(isCollaboratingAtom, isCollaborating);
|
appJotaiStore.set(isCollaboratingAtom, isCollaborating);
|
||||||
};
|
};
|
||||||
|
|
||||||
private onUnload = () => {
|
private onUnload = () => {
|
||||||
@ -804,7 +804,7 @@ class Collab extends PureComponent<Props, CollabState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
jotaiStore.set(collabDialogShownAtom, false);
|
appJotaiStore.set(collabDialogShownAtom, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
setUsername = (username: string) => {
|
setUsername = (username: string) => {
|
||||||
|
@ -10,13 +10,13 @@ import {
|
|||||||
shareWindows,
|
shareWindows,
|
||||||
} from "../../components/icons";
|
} from "../../components/icons";
|
||||||
import { ToolButton } from "../../components/ToolButton";
|
import { ToolButton } from "../../components/ToolButton";
|
||||||
import { t } from "../../i18n";
|
|
||||||
import "./RoomDialog.scss";
|
import "./RoomDialog.scss";
|
||||||
import Stack from "../../components/Stack";
|
import Stack from "../../components/Stack";
|
||||||
import { AppState } from "../../types";
|
import { AppState } from "../../types";
|
||||||
import { trackEvent } from "../../analytics";
|
import { trackEvent } from "../../analytics";
|
||||||
import { getFrame } from "../../utils";
|
import { getFrame } from "../../utils";
|
||||||
import DialogActionButton from "../../components/DialogActionButton";
|
import DialogActionButton from "../../components/DialogActionButton";
|
||||||
|
import { useI18n } from "../../i18n";
|
||||||
|
|
||||||
const getShareIcon = () => {
|
const getShareIcon = () => {
|
||||||
const navigator = window.navigator as any;
|
const navigator = window.navigator as any;
|
||||||
@ -51,6 +51,7 @@ const RoomDialog = ({
|
|||||||
setErrorMessage: (message: string) => void;
|
setErrorMessage: (message: string) => void;
|
||||||
theme: AppState["theme"];
|
theme: AppState["theme"];
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useI18n();
|
||||||
const roomLinkInput = useRef<HTMLInputElement>(null);
|
const roomLinkInput = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const copyRoomLink = async () => {
|
const copyRoomLink = async () => {
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { PlusPromoIcon } from "../../components/icons";
|
import { PlusPromoIcon } from "../../components/icons";
|
||||||
import { t } from "../../i18n";
|
import { useI18n } from "../../i18n";
|
||||||
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
import { WelcomeScreen } from "../../packages/excalidraw/index";
|
||||||
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
import { isExcalidrawPlusSignedUser } from "../app_constants";
|
||||||
|
|
||||||
export const AppWelcomeScreen: React.FC<{
|
export const AppWelcomeScreen: React.FC<{
|
||||||
setCollabDialogShown: (toggle: boolean) => any;
|
setCollabDialogShown: (toggle: boolean) => any;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useI18n();
|
||||||
let headingContent;
|
let headingContent;
|
||||||
|
|
||||||
if (isExcalidrawPlusSignedUser) {
|
if (isExcalidrawPlusSignedUser) {
|
||||||
|
@ -1,17 +1,21 @@
|
|||||||
import { shield } from "../../components/icons";
|
import { shield } from "../../components/icons";
|
||||||
import { Tooltip } from "../../components/Tooltip";
|
import { Tooltip } from "../../components/Tooltip";
|
||||||
import { t } from "../../i18n";
|
import { useI18n } from "../../i18n";
|
||||||
|
|
||||||
export const EncryptedIcon = () => (
|
export const EncryptedIcon = () => {
|
||||||
<a
|
const { t } = useI18n();
|
||||||
className="encrypted-icon tooltip"
|
|
||||||
href="https://blog.excalidraw.com/end-to-end-encryption/"
|
return (
|
||||||
target="_blank"
|
<a
|
||||||
rel="noopener noreferrer"
|
className="encrypted-icon tooltip"
|
||||||
aria-label={t("encrypted.link")}
|
href="https://blog.excalidraw.com/end-to-end-encryption/"
|
||||||
>
|
target="_blank"
|
||||||
<Tooltip label={t("encrypted.tooltip")} long={true}>
|
rel="noopener noreferrer"
|
||||||
{shield}
|
aria-label={t("encrypted.link")}
|
||||||
</Tooltip>
|
>
|
||||||
</a>
|
<Tooltip label={t("encrypted.tooltip")} long={true}>
|
||||||
);
|
{shield}
|
||||||
|
</Tooltip>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@ -6,7 +6,7 @@ import { loadFirebaseStorage, saveFilesToFirebase } from "../data/firebase";
|
|||||||
import { FileId, NonDeletedExcalidrawElement } from "../../element/types";
|
import { FileId, NonDeletedExcalidrawElement } from "../../element/types";
|
||||||
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
|
import { AppState, BinaryFileData, BinaryFiles } from "../../types";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { t } from "../../i18n";
|
import { useI18n } from "../../i18n";
|
||||||
import { excalidrawPlusIcon } from "./icons";
|
import { excalidrawPlusIcon } from "./icons";
|
||||||
import { encryptData, generateEncryptionKey } from "../../data/encryption";
|
import { encryptData, generateEncryptionKey } from "../../data/encryption";
|
||||||
import { isInitializedImageElement } from "../../element/typeChecks";
|
import { isInitializedImageElement } from "../../element/typeChecks";
|
||||||
@ -79,6 +79,7 @@ export const ExportToExcalidrawPlus: React.FC<{
|
|||||||
files: BinaryFiles;
|
files: BinaryFiles;
|
||||||
onError: (error: Error) => void;
|
onError: (error: Error) => void;
|
||||||
}> = ({ elements, appState, files, onError }) => {
|
}> = ({ elements, appState, files, onError }) => {
|
||||||
|
const { t } = useI18n();
|
||||||
return (
|
return (
|
||||||
<Card color="primary">
|
<Card color="primary">
|
||||||
<div className="Card-icon">{excalidrawPlusIcon}</div>
|
<div className="Card-icon">{excalidrawPlusIcon}</div>
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
import { useAtom } from "jotai";
|
import { useSetAtom } from "jotai";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { langCodeAtom } from "..";
|
import { appLangCodeAtom } from "..";
|
||||||
import * as i18n from "../../i18n";
|
import { defaultLang, useI18n } from "../../i18n";
|
||||||
import { languages } from "../../i18n";
|
import { languages } from "../../i18n";
|
||||||
|
|
||||||
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
export const LanguageList = ({ style }: { style?: React.CSSProperties }) => {
|
||||||
const [langCode, setLangCode] = useAtom(langCodeAtom);
|
const { t, langCode } = useI18n();
|
||||||
|
const setLangCode = useSetAtom(appLangCodeAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
className="dropdown-select dropdown-select__language"
|
className="dropdown-select dropdown-select__language"
|
||||||
onChange={({ target }) => setLangCode(target.value)}
|
onChange={({ target }) => setLangCode(target.value)}
|
||||||
value={langCode}
|
value={langCode}
|
||||||
aria-label={i18n.t("buttons.selectLanguage")}
|
aria-label={t("buttons.selectLanguage")}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<option key={i18n.defaultLang.code} value={i18n.defaultLang.code}>
|
<option key={defaultLang.code} value={defaultLang.code}>
|
||||||
{i18n.defaultLang.label}
|
{defaultLang.label}
|
||||||
</option>
|
</option>
|
||||||
{languages.map((lang) => (
|
{languages.map((lang) => (
|
||||||
<option key={lang.code} value={lang.code}>
|
<option key={lang.code} value={lang.code}>
|
||||||
|
@ -75,13 +75,14 @@ import { loadFilesFromFirebase } from "./data/firebase";
|
|||||||
import { LocalData } from "./data/LocalData";
|
import { LocalData } from "./data/LocalData";
|
||||||
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
import { isBrowserStorageStateNewer } from "./data/tabSync";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
|
||||||
import { jotaiStore, useAtomWithInitialValue } from "../jotai";
|
|
||||||
import { reconcileElements } from "./collab/reconciliation";
|
import { reconcileElements } from "./collab/reconciliation";
|
||||||
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
|
import { parseLibraryTokensFromUrl, useHandleLibrary } from "../data/library";
|
||||||
import { AppMainMenu } from "./components/AppMainMenu";
|
import { AppMainMenu } from "./components/AppMainMenu";
|
||||||
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
import { AppWelcomeScreen } from "./components/AppWelcomeScreen";
|
||||||
import { AppFooter } from "./components/AppFooter";
|
import { AppFooter } from "./components/AppFooter";
|
||||||
|
import { atom, Provider, useAtom, useAtomValue } from "jotai";
|
||||||
|
import { useAtomWithInitialValue } from "../jotai";
|
||||||
|
import { appJotaiStore } from "./app-jotai";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
|
||||||
@ -226,15 +227,15 @@ const initializeScene = async (opts: {
|
|||||||
return { scene: null, isExternalScene: false };
|
return { scene: null, isExternalScene: false };
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentLangCode = languageDetector.detect() || defaultLang.code;
|
const detectedLangCode = languageDetector.detect() || defaultLang.code;
|
||||||
|
export const appLangCodeAtom = atom(
|
||||||
export const langCodeAtom = atom(
|
Array.isArray(detectedLangCode) ? detectedLangCode[0] : detectedLangCode,
|
||||||
Array.isArray(currentLangCode) ? currentLangCode[0] : currentLangCode,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const ExcalidrawWrapper = () => {
|
const ExcalidrawWrapper = () => {
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
const [langCode, setLangCode] = useAtom(langCodeAtom);
|
const [langCode, setLangCode] = useAtom(appLangCodeAtom);
|
||||||
|
|
||||||
// initial state
|
// initial state
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@ -683,7 +684,7 @@ const ExcalidrawWrapper = () => {
|
|||||||
const ExcalidrawApp = () => {
|
const ExcalidrawApp = () => {
|
||||||
return (
|
return (
|
||||||
<TopErrorBoundary>
|
<TopErrorBoundary>
|
||||||
<Provider unstable_createStore={() => jotaiStore}>
|
<Provider unstable_createStore={() => appJotaiStore}>
|
||||||
<ExcalidrawWrapper />
|
<ExcalidrawWrapper />
|
||||||
</Provider>
|
</Provider>
|
||||||
</TopErrorBoundary>
|
</TopErrorBoundary>
|
||||||
|
16
src/i18n.ts
16
src/i18n.ts
@ -1,6 +1,8 @@
|
|||||||
import fallbackLangData from "./locales/en.json";
|
import fallbackLangData from "./locales/en.json";
|
||||||
import percentages from "./locales/percentages.json";
|
import percentages from "./locales/percentages.json";
|
||||||
import { ENV } from "./constants";
|
import { ENV } from "./constants";
|
||||||
|
import { jotaiScope, jotaiStore } from "./jotai";
|
||||||
|
import { atom, useAtomValue } from "jotai";
|
||||||
|
|
||||||
const COMPLETION_THRESHOLD = 85;
|
const COMPLETION_THRESHOLD = 85;
|
||||||
|
|
||||||
@ -99,6 +101,8 @@ export const setLanguage = async (lang: Language) => {
|
|||||||
currentLangData = fallbackLangData;
|
currentLangData = fallbackLangData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jotaiStore.set(editorLangCodeAtom, lang.code);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLanguage = () => currentLang;
|
export const getLanguage = () => currentLang;
|
||||||
@ -143,3 +147,15 @@ export const t = (
|
|||||||
}
|
}
|
||||||
return translation;
|
return translation;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @private atom used solely to rerender components using `useI18n` hook */
|
||||||
|
const editorLangCodeAtom = atom(defaultLang.code);
|
||||||
|
|
||||||
|
// Should be used in components that fall under these cases:
|
||||||
|
// - component is rendered as an <Excalidraw> child
|
||||||
|
// - component is rendered internally by <Excalidraw>, but the component
|
||||||
|
// is memoized w/o being updated on `langCode`, `AppState`, or `UIAppState`
|
||||||
|
export const useI18n = () => {
|
||||||
|
const langCode = useAtomValue(editorLangCodeAtom, jotaiScope);
|
||||||
|
return { t, langCode };
|
||||||
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { unstable_createStore, useAtom, WritableAtom } from "jotai";
|
import { PrimitiveAtom, unstable_createStore, useAtom } from "jotai";
|
||||||
import { useLayoutEffect } from "react";
|
import { useLayoutEffect } from "react";
|
||||||
|
|
||||||
export const jotaiScope = Symbol();
|
export const jotaiScope = Symbol();
|
||||||
@ -6,7 +6,7 @@ export const jotaiStore = unstable_createStore();
|
|||||||
|
|
||||||
export const useAtomWithInitialValue = <
|
export const useAtomWithInitialValue = <
|
||||||
T extends unknown,
|
T extends unknown,
|
||||||
A extends WritableAtom<T, T>,
|
A extends PrimitiveAtom<T>,
|
||||||
>(
|
>(
|
||||||
atom: A,
|
atom: A,
|
||||||
initialValue: T | (() => T),
|
initialValue: T | (() => T),
|
||||||
|
@ -15,6 +15,8 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
- Expose `useI18n()` hook return an object containing `t()` i18n helper and current `langCode`. You can use this in components you render as `<Excalidraw>` children to render any of our i18n locale strings. [#6224](https://github.com/excalidraw/excalidraw/pull/6224)
|
||||||
|
|
||||||
- [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) API now takes an optional parameter `opts` which currently supports the below attributes
|
- [`restoreElements`](https://docs.excalidraw.com/docs/@excalidraw/excalidraw/api/utils/restore#restoreelements) API now takes an optional parameter `opts` which currently supports the below attributes
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -87,8 +87,8 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InitializeApp langCode={langCode} theme={theme}>
|
<Provider unstable_createStore={() => jotaiStore} scope={jotaiScope}>
|
||||||
<Provider unstable_createStore={() => jotaiStore} scope={jotaiScope}>
|
<InitializeApp langCode={langCode} theme={theme}>
|
||||||
<App
|
<App
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
initialData={initialData}
|
initialData={initialData}
|
||||||
@ -118,8 +118,8 @@ const ExcalidrawBase = (props: ExcalidrawProps) => {
|
|||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</App>
|
</App>
|
||||||
</Provider>
|
</InitializeApp>
|
||||||
</InitializeApp>
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -198,7 +198,7 @@ export {
|
|||||||
isInvisiblySmallElement,
|
isInvisiblySmallElement,
|
||||||
getNonDeletedElements,
|
getNonDeletedElements,
|
||||||
} from "../../element";
|
} from "../../element";
|
||||||
export { defaultLang, languages } from "../../i18n";
|
export { defaultLang, useI18n, languages } from "../../i18n";
|
||||||
export {
|
export {
|
||||||
restore,
|
restore,
|
||||||
restoreAppState,
|
restoreAppState,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user