feat(gui): add i18next and start localization
This commit is contained in:
parent
9a0d2051fb
commit
5bfaddd388
|
@ -16,10 +16,13 @@
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"browser-bunyan": "^1.8.0",
|
"browser-bunyan": "^1.8.0",
|
||||||
|
"i18next": "^22.4.10",
|
||||||
|
"i18next-browser-languagedetector": "^7.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"noicejs": "^5.0.0-3",
|
"noicejs": "^5.0.0-3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"react-i18next": "^12.2.0",
|
||||||
"react-query": "^3.39.2",
|
"react-query": "^3.39.2",
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"semver": "^7.3.8",
|
"semver": "^7.3.8",
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { ArrowLeft, ArrowRight, Blender, Brush, ContentCopy, Delete, Download, Z
|
||||||
import { Box, Card, CardContent, CardMedia, Grid, IconButton, Menu, MenuItem, Paper, Tooltip } from '@mui/material';
|
import { Box, Card, CardContent, CardMedia, Grid, IconButton, Menu, MenuItem, Paper, Tooltip } from '@mui/material';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useContext, useState } from 'react';
|
import { useContext, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useHash } from 'react-use/lib/useHash';
|
import { useHash } from 'react-use/lib/useHash';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
|
|
||||||
|
@ -96,6 +97,7 @@ export function ImageCard(props: ImageCardProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [index, setIndex] = useState(0);
|
const [index, setIndex] = useState(0);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const model = mustDefault(MODEL_LABELS[params.model], params.model);
|
const model = mustDefault(MODEL_LABELS[params.model], params.model);
|
||||||
const scheduler = mustDefault(SCHEDULER_LABELS[params.scheduler], params.scheduler);
|
const scheduler = mustDefault(SCHEDULER_LABELS[params.scheduler], params.scheduler);
|
||||||
|
@ -110,7 +112,7 @@ export function ImageCard(props: ImageCardProps) {
|
||||||
<Box textAlign='center'>
|
<Box textAlign='center'>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<GridItem xs={4}>
|
<GridItem xs={4}>
|
||||||
<Tooltip title='Previous'>
|
<Tooltip title={t('tooltip.previous')}>
|
||||||
<IconButton onClick={() => {
|
<IconButton onClick={() => {
|
||||||
const prevIndex = index - 1;
|
const prevIndex = index - 1;
|
||||||
if (prevIndex < 0) {
|
if (prevIndex < 0) {
|
||||||
|
@ -127,7 +129,7 @@ export function ImageCard(props: ImageCardProps) {
|
||||||
{visibleIndex(index)} of {outputs.length}
|
{visibleIndex(index)} of {outputs.length}
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem xs={4}>
|
<GridItem xs={4}>
|
||||||
<Tooltip title='Next'>
|
<Tooltip title={t('tooltip.next')}>
|
||||||
<IconButton onClick={() => {
|
<IconButton onClick={() => {
|
||||||
setIndex((index + 1) % outputs.length);
|
setIndex((index + 1) % outputs.length);
|
||||||
}}>
|
}}>
|
||||||
|
@ -145,35 +147,35 @@ export function ImageCard(props: ImageCardProps) {
|
||||||
<Box textAlign='left'>{params.prompt}</Box>
|
<Box textAlign='left'>{params.prompt}</Box>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem xs={2}>
|
<GridItem xs={2}>
|
||||||
<Tooltip title='Save'>
|
<Tooltip title={t('tooltip.save')}>
|
||||||
<IconButton onClick={downloadImage}>
|
<IconButton onClick={downloadImage}>
|
||||||
<Download />
|
<Download />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem xs={2}>
|
<GridItem xs={2}>
|
||||||
<Tooltip title='Img2img'>
|
<Tooltip title={t('tab.img2img')}>
|
||||||
<IconButton onClick={copySourceToImg2Img}>
|
<IconButton onClick={copySourceToImg2Img}>
|
||||||
<ContentCopy />
|
<ContentCopy />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem xs={2}>
|
<GridItem xs={2}>
|
||||||
<Tooltip title='Inpaint'>
|
<Tooltip title={t('tab.inpaint')}>
|
||||||
<IconButton onClick={copySourceToInpaint}>
|
<IconButton onClick={copySourceToInpaint}>
|
||||||
<Brush />
|
<Brush />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem xs={2}>
|
<GridItem xs={2}>
|
||||||
<Tooltip title='Upscale'>
|
<Tooltip title={t('tab.upscale')}>
|
||||||
<IconButton onClick={copySourceToUpscale}>
|
<IconButton onClick={copySourceToUpscale}>
|
||||||
<ZoomOutMap />
|
<ZoomOutMap />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem xs={2}>
|
<GridItem xs={2}>
|
||||||
<Tooltip title='Blend'>
|
<Tooltip title={t('tab.blend')}>
|
||||||
<IconButton onClick={(event) => {
|
<IconButton onClick={(event) => {
|
||||||
setAnchor(event.currentTarget);
|
setAnchor(event.currentTarget);
|
||||||
}}>
|
}}>
|
||||||
|
@ -194,7 +196,7 @@ export function ImageCard(props: ImageCardProps) {
|
||||||
</Menu>
|
</Menu>
|
||||||
</GridItem>
|
</GridItem>
|
||||||
<GridItem xs={2}>
|
<GridItem xs={2}>
|
||||||
<Tooltip title='Delete'>
|
<Tooltip title={t('tooltip.delete')}>
|
||||||
<IconButton onClick={deleteImage}>
|
<IconButton onClick={deleteImage}>
|
||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { doesExist, mustExist } from '@apextoaster/js-utils';
|
||||||
import { Grid, Typography } from '@mui/material';
|
import { Grid, Typography } from '@mui/material';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
|
|
||||||
import { StateContext } from '../state.js';
|
import { StateContext } from '../state.js';
|
||||||
|
@ -15,6 +16,8 @@ export function ImageHistory() {
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
const removeHistory = useStore(mustExist(useContext(StateContext)), (state) => state.removeHistory);
|
const removeHistory = useStore(mustExist(useContext(StateContext)), (state) => state.removeHistory);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const children = [];
|
const children = [];
|
||||||
|
|
||||||
if (loading.length > 0) {
|
if (loading.length > 0) {
|
||||||
|
@ -25,7 +28,7 @@ export function ImageHistory() {
|
||||||
children.push(...history.map((item) => <ImageCard key={`history-${item.outputs[0].key}`} value={item} onDelete={removeHistory} />));
|
children.push(...history.map((item) => <ImageCard key={`history-${item.outputs[0].key}`} value={item} onDelete={removeHistory} />));
|
||||||
} else {
|
} else {
|
||||||
if (doesExist(loading) === false) {
|
if (doesExist(loading) === false) {
|
||||||
children.push(<Typography>No results. Press Generate.</Typography>);
|
children.push(<Typography>{t('history.empty')}</Typography>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Box, Button, Card, CardContent, CircularProgress, Typography } from '@m
|
||||||
import { Stack } from '@mui/system';
|
import { Stack } from '@mui/system';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useContext, useEffect } from 'react';
|
import { useContext, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useMutation, useQuery } from 'react-query';
|
import { useMutation, useQuery } from 'react-query';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ export function LoadingCard(props: LoadingCardProps) {
|
||||||
const pushHistory = useStore(state, (s) => s.pushHistory);
|
const pushHistory = useStore(state, (s) => s.pushHistory);
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
const setReady = useStore(state, (s) => s.setReady);
|
const setReady = useStore(state, (s) => s.setReady);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const cancel = useMutation(() => client.cancel(loading.outputs[index].key));
|
const cancel = useMutation(() => client.cancel(loading.outputs[index].key));
|
||||||
const ready = useQuery(`ready-${loading.outputs[index].key}`, () => client.ready(loading.outputs[index].key), {
|
const ready = useQuery(`ready-${loading.outputs[index].key}`, () => client.ready(loading.outputs[index].key), {
|
||||||
|
@ -63,7 +65,7 @@ export function LoadingCard(props: LoadingCardProps) {
|
||||||
const progress = getProgress();
|
const progress = getProgress();
|
||||||
if (progress > steps) {
|
if (progress > steps) {
|
||||||
// steps was not complete, show 99% until done
|
// steps was not complete, show 99% until done
|
||||||
return 'many';
|
return t('loading.unknown');
|
||||||
}
|
}
|
||||||
|
|
||||||
return steps.toFixed(0);
|
return steps.toFixed(0);
|
||||||
|
@ -112,8 +114,11 @@ export function LoadingCard(props: LoadingCardProps) {
|
||||||
sx={{ alignItems: 'center' }}
|
sx={{ alignItems: 'center' }}
|
||||||
>
|
>
|
||||||
{renderProgress()}
|
{renderProgress()}
|
||||||
<Typography>{getProgress()} of {getTotal()} steps</Typography>
|
<Typography>{t('loading.progress', {
|
||||||
<Button onClick={() => cancel.mutate()}>Cancel</Button>
|
current: getProgress(),
|
||||||
|
total: getTotal(),
|
||||||
|
})}</Typography>
|
||||||
|
<Button onClick={() => cancel.mutate()}>{t('loading.cancel')}</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
import { mustDefault, mustExist, timeout } from '@apextoaster/js-utils';
|
import { mustDefault, mustExist, timeout } from '@apextoaster/js-utils';
|
||||||
|
import { createLogger } from 'browser-bunyan';
|
||||||
|
import i18n from 'i18next';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { I18nextProvider, initReactI18next } from 'react-i18next';
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||||
import { satisfies } from 'semver';
|
import { satisfies } from 'semver';
|
||||||
import { createStore } from 'zustand';
|
import { createStore } from 'zustand';
|
||||||
import { createJSONStorage, persist } from 'zustand/middleware';
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||||
import { createLogger } from 'browser-bunyan';
|
|
||||||
|
|
||||||
import { makeClient } from './client.js';
|
import { makeClient } from './client.js';
|
||||||
import { ParamsVersionError } from './components/error/ParamsVersion.js';
|
import { ParamsVersionError } from './components/error/ParamsVersion.js';
|
||||||
|
@ -14,7 +17,17 @@ import { ServerParamsError } from './components/error/ServerParams.js';
|
||||||
import { OnnxError } from './components/OnnxError.js';
|
import { OnnxError } from './components/OnnxError.js';
|
||||||
import { OnnxWeb } from './components/OnnxWeb.js';
|
import { OnnxWeb } from './components/OnnxWeb.js';
|
||||||
import { getApiRoot, loadConfig, mergeConfig, PARAM_VERSION } from './config.js';
|
import { getApiRoot, loadConfig, mergeConfig, PARAM_VERSION } from './config.js';
|
||||||
import { ClientContext, ConfigContext, createStateSlices, OnnxState, STATE_VERSION, StateContext, LoggerContext, STATE_KEY } from './state.js';
|
import {
|
||||||
|
ClientContext,
|
||||||
|
ConfigContext,
|
||||||
|
createStateSlices,
|
||||||
|
LoggerContext,
|
||||||
|
OnnxState,
|
||||||
|
STATE_KEY,
|
||||||
|
STATE_VERSION,
|
||||||
|
StateContext,
|
||||||
|
} from './state.js';
|
||||||
|
import { I18N_STRINGS } from './strings/all.js';
|
||||||
|
|
||||||
export const INITIAL_LOAD_TIMEOUT = 5_000;
|
export const INITIAL_LOAD_TIMEOUT = 5_000;
|
||||||
|
|
||||||
|
@ -37,6 +50,19 @@ export async function main() {
|
||||||
if (satisfies(version, PARAM_VERSION)) {
|
if (satisfies(version, PARAM_VERSION)) {
|
||||||
const completeConfig = mergeConfig(config, params);
|
const completeConfig = mergeConfig(config, params);
|
||||||
|
|
||||||
|
// prep i18next
|
||||||
|
await i18n
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.use(initReactI18next)
|
||||||
|
.init({
|
||||||
|
debug: true,
|
||||||
|
fallbackLng: 'en',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false, // not needed for react as it escapes by default
|
||||||
|
},
|
||||||
|
resources: I18N_STRINGS,
|
||||||
|
});
|
||||||
|
|
||||||
// prep zustand with a slice for each tab, using local storage
|
// prep zustand with a slice for each tab, using local storage
|
||||||
const {
|
const {
|
||||||
createBrushSlice,
|
createBrushSlice,
|
||||||
|
@ -106,9 +132,11 @@ export async function main() {
|
||||||
<ClientContext.Provider value={client}>
|
<ClientContext.Provider value={client}>
|
||||||
<ConfigContext.Provider value={completeConfig}>
|
<ConfigContext.Provider value={completeConfig}>
|
||||||
<LoggerContext.Provider value={logger}>
|
<LoggerContext.Provider value={logger}>
|
||||||
<StateContext.Provider value={state}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<OnnxWeb />
|
<StateContext.Provider value={state}>
|
||||||
</StateContext.Provider>
|
<OnnxWeb />
|
||||||
|
</StateContext.Provider>
|
||||||
|
</I18nextProvider>
|
||||||
</LoggerContext.Provider>
|
</LoggerContext.Provider>
|
||||||
</ConfigContext.Provider>
|
</ConfigContext.Provider>
|
||||||
</ClientContext.Provider>
|
</ClientContext.Provider>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { I18N_STRINGS_EN, RequiredStrings } from './en.js';
|
||||||
|
import { I18N_STRINGS_FR } from './fr.js';
|
||||||
|
|
||||||
|
export const I18N_STRINGS: Record<string, RequiredStrings> = {
|
||||||
|
...I18N_STRINGS_EN,
|
||||||
|
...I18N_STRINGS_FR,
|
||||||
|
};
|
|
@ -0,0 +1,31 @@
|
||||||
|
export const I18N_STRINGS_EN = {
|
||||||
|
en: {
|
||||||
|
translation: {
|
||||||
|
history: {
|
||||||
|
empty: 'No results. Press Generate to create an image.',
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
cancel: 'Cancel',
|
||||||
|
progress: '{{current}} of {{total}} steps',
|
||||||
|
unknown: 'many',
|
||||||
|
},
|
||||||
|
tab: {
|
||||||
|
blend: 'Blend',
|
||||||
|
img2img: 'Img2img',
|
||||||
|
inpaint: 'Inpaint',
|
||||||
|
txt2txt: 'Txt2txt',
|
||||||
|
txt2img: 'Txt2img',
|
||||||
|
upscale: 'Upscale',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
delete: 'Delete',
|
||||||
|
next: 'EN Next',
|
||||||
|
previous: 'EN Previous',
|
||||||
|
save: 'Save',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// easy way to make sure all locales have the complete set of strings
|
||||||
|
export type RequiredStrings = typeof I18N_STRINGS_EN['en'];
|
|
@ -0,0 +1,20 @@
|
||||||
|
export const I18N_STRINGS_FR = {
|
||||||
|
fr: {
|
||||||
|
translation: {
|
||||||
|
tab: {
|
||||||
|
blend: 'Blend',
|
||||||
|
img2img: 'Img2img',
|
||||||
|
inpaint: 'Inpaint',
|
||||||
|
txt2txt: 'Txt2txt',
|
||||||
|
txt2img: 'Txt2img',
|
||||||
|
upscale: 'Upscale',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
delete: 'Delete',
|
||||||
|
next: 'FR-Next',
|
||||||
|
previous: 'FR-Previous',
|
||||||
|
save: 'Save',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
|
@ -66,6 +66,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.11"
|
regenerator-runtime "^0.13.11"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.19.4":
|
||||||
|
version "7.21.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.0.tgz#5b55c9d394e5fcf304909a8b00c07dc217b56673"
|
||||||
|
integrity sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.13.11"
|
||||||
|
|
||||||
"@babel/types@^7.18.6":
|
"@babel/types@^7.18.6":
|
||||||
version "7.20.7"
|
version "7.20.7"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f"
|
||||||
|
@ -1852,11 +1859,32 @@ html-escaper@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||||
|
|
||||||
|
html-parse-stringify@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||||
|
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||||
|
dependencies:
|
||||||
|
void-elements "3.1.0"
|
||||||
|
|
||||||
hyphenate-style-name@^1.0.3:
|
hyphenate-style-name@^1.0.3:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
|
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
|
||||||
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
|
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
|
||||||
|
|
||||||
|
i18next-browser-languagedetector@^7.0.1:
|
||||||
|
version "7.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-7.0.1.tgz#ead34592edc96c6c3a618a51cb57ad027c5b5d87"
|
||||||
|
integrity sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.19.4"
|
||||||
|
|
||||||
|
i18next@^22.4.10:
|
||||||
|
version "22.4.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/i18next/-/i18next-22.4.10.tgz#cfbfc412c6bc83e3c16564f47e6a5c145255960e"
|
||||||
|
integrity sha512-3EqgGK6fAJRjnGgfkNSStl4mYLCjUoJID338yVyLMj5APT67HUtWoqSayZewiiC5elzMUB1VEUwcmSCoeQcNEA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.20.6"
|
||||||
|
|
||||||
ignore@^5.2.0:
|
ignore@^5.2.0:
|
||||||
version "5.2.4"
|
version "5.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
|
||||||
|
@ -2575,6 +2603,14 @@ react-dom@^18.2.0:
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.0"
|
scheduler "^0.23.0"
|
||||||
|
|
||||||
|
react-i18next@^12.2.0:
|
||||||
|
version "12.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.2.0.tgz#010e3f6070b8d700442947233352ebe4b252d7a1"
|
||||||
|
integrity sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.20.6"
|
||||||
|
html-parse-stringify "^3.0.1"
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
|
@ -3095,6 +3131,11 @@ v8-to-istanbul@^9.0.0:
|
||||||
"@types/istanbul-lib-coverage" "^2.0.1"
|
"@types/istanbul-lib-coverage" "^2.0.1"
|
||||||
convert-source-map "^1.6.0"
|
convert-source-map "^1.6.0"
|
||||||
|
|
||||||
|
void-elements@3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||||
|
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
|
||||||
|
|
||||||
which-boxed-primitive@^1.0.2:
|
which-boxed-primitive@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||||
|
|
Loading…
Reference in New Issue