1
0
Fork 0

feat(gui): translate most of the client

This commit is contained in:
Sean Sube 2023-03-02 17:24:45 -06:00
parent 5bfaddd388
commit 3760093617
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
27 changed files with 843 additions and 219 deletions

View File

@ -9,7 +9,6 @@ import { useStore } from 'zustand';
import { ImageResponse } from '../client.js';
import { BLEND_SOURCES, ConfigContext, StateContext } from '../state.js';
import { MODEL_LABELS, SCHEDULER_LABELS } from '../strings.js';
import { range, visibleIndex } from '../utils.js';
export interface ImageCardProps {
@ -99,8 +98,12 @@ export function ImageCard(props: ImageCardProps) {
const [index, setIndex] = useState(0);
const { t } = useTranslation();
const model = mustDefault(MODEL_LABELS[params.model], params.model);
const scheduler = mustDefault(SCHEDULER_LABELS[params.scheduler], params.scheduler);
function getLabel(key: string, name: string) {
return mustDefault(t(`${key}.${name}`), name);
}
const model = getLabel('model', params.model);
const scheduler = getLabel('scheduler', params.scheduler);
return <Card sx={{ maxWidth: config.params.width.default }} elevation={2}>
<CardMedia sx={{ height: config.params.height.default }}
@ -138,11 +141,11 @@ export function ImageCard(props: ImageCardProps) {
</Tooltip>
</GridItem>
<GridItem xs={4}>Model: {model}</GridItem>
<GridItem xs={4}>Scheduler: {scheduler}</GridItem>
<GridItem xs={4}>Seed: {params.seed}</GridItem>
<GridItem xs={4}>CFG: {params.cfg}</GridItem>
<GridItem xs={4}>Steps: {params.steps}</GridItem>
<GridItem xs={4}>Size: {size.width}x{size.height}</GridItem>
<GridItem xs={4}>{t('parameter.scheduler')}: {scheduler}</GridItem>
<GridItem xs={4}>{t('parameter.seed')}: {params.seed}</GridItem>
<GridItem xs={4}>{t('parameter.cfg')}: {params.cfg}</GridItem>
<GridItem xs={4}>{t('parameter.steps')}: {params.steps}</GridItem>
<GridItem xs={4}>{t('parameter.size')}: {size.width}x{size.height}</GridItem>
<GridItem xs={12}>
<Box textAlign='left'>{params.prompt}</Box>
</GridItem>

View File

@ -0,0 +1,8 @@
import { Link, Typography } from '@mui/material';
import * as React from 'react';
export function Logo() {
return <Typography variant='h3' gutterBottom>
<Link href='https://github.com/ssube/onnx-web' target='_blank' underline='hover'>ONNX Web</Link>
</Typography>;
}

View File

@ -1,7 +1,9 @@
import { Box, Button, Container, Link, Stack, Typography } from '@mui/material';
import { Box, Button, Container, Stack, Typography } from '@mui/material';
import * as React from 'react';
import { ReactNode } from 'react';
import { STATE_KEY } from '../state';
import { STATE_KEY } from '../state.js';
import { Logo } from './Logo.js';
export interface OnnxErrorProps {
children?: ReactNode;
@ -19,9 +21,7 @@ export function OnnxError(props: OnnxErrorProps) {
return (
<Container>
<Box sx={{ my: 4 }}>
<Typography variant='h3' gutterBottom>
<Link href='https://github.com/ssube/onnx-web' target='_blank' underline='hover'>ONNX Web</Link>
</Typography>
<Logo />
</Box>
<Box sx={{ my: 4 }}>
<Stack spacing={2}>

View File

@ -6,6 +6,7 @@ import { useHash } from 'react-use/lib/useHash';
import { ModelControl } from './control/ModelControl.js';
import { ImageHistory } from './ImageHistory.js';
import { Logo } from './Logo.js';
import { Blend } from './tab/Blend.js';
import { Img2Img } from './tab/Img2Img.js';
import { Inpaint } from './tab/Inpaint.js';
@ -41,9 +42,7 @@ export function OnnxWeb() {
return (
<Container>
<Box sx={{ my: 4 }}>
<Typography variant='h3' gutterBottom>
<Link href='https://github.com/ssube/onnx-web' target='_blank' underline='hover'>ONNX Web</Link>
</Typography>
<Logo />
</Box>
<Box sx={{ mx: 4, my: 4 }}>
<ModelControl />

View File

@ -3,13 +3,13 @@ import { Casino } from '@mui/icons-material';
import { Button, Stack } from '@mui/material';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useStore } from 'zustand';
import { BaseImgParams } from '../../client.js';
import { STALE_TIME } from '../../config.js';
import { ClientContext, ConfigContext, OnnxState, StateContext } from '../../state.js';
import { SCHEDULER_LABELS } from '../../strings.js';
import { NumericField } from '../input/NumericField.js';
import { PromptInput } from '../input/PromptInput.js';
import { QueryList } from '../input/QueryList.js';
@ -27,6 +27,7 @@ export function ImageControl(props: ImageControlProps) {
const { params } = mustExist(useContext(ConfigContext));
const state = mustExist(useContext(StateContext));
const controlState = useStore(state, props.selector);
const { t } = useTranslation();
const client = mustExist(useContext(ClientContext));
const schedulers = useQuery('schedulers', async () => client.schedulers(), {
@ -37,8 +38,8 @@ export function ImageControl(props: ImageControlProps) {
<Stack direction='row' spacing={4}>
<QueryList
id='schedulers'
labels={SCHEDULER_LABELS}
name='Scheduler'
labelKey='scheduler'
name={t('parameter.scheduler')}
query={{
result: schedulers,
}}
@ -54,7 +55,7 @@ export function ImageControl(props: ImageControlProps) {
/>
<NumericField
decimal
label='Eta'
label={t('parameter.eta')}
min={params.eta.min}
max={params.eta.max}
step={params.eta.step}
@ -69,7 +70,7 @@ export function ImageControl(props: ImageControlProps) {
}}
/>
<NumericField
label='Batch Size'
label={t('parameter.batch')}
min={params.batch.min}
max={params.batch.max}
step={params.batch.step}
@ -87,7 +88,7 @@ export function ImageControl(props: ImageControlProps) {
<Stack direction='row' spacing={4}>
<NumericField
decimal
label='CFG'
label={t('parameter.cfg')}
min={params.cfg.min}
max={params.cfg.max}
step={params.cfg.step}
@ -102,7 +103,7 @@ export function ImageControl(props: ImageControlProps) {
}}
/>
<NumericField
label='Steps'
label={t('parameter.steps')}
min={params.steps.min}
max={params.steps.max}
step={params.steps.step}
@ -117,7 +118,7 @@ export function ImageControl(props: ImageControlProps) {
}}
/>
<NumericField
label='Seed'
label={t('parameter.seed')}
min={params.seed.min}
max={params.seed.max}
step={params.seed.step}
@ -144,7 +145,7 @@ export function ImageControl(props: ImageControlProps) {
}
}}
>
New Seed
{t('parameter.newSeed')}
</Button>
</Stack>
<PromptInput

View File

@ -2,12 +2,12 @@ import { mustExist } from '@apextoaster/js-utils';
import { Checkbox, FormControlLabel, Stack } from '@mui/material';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useStore } from 'zustand';
import { STALE_TIME } from '../../config.js';
import { ClientContext, StateContext } from '../../state.js';
import { INVERSION_LABELS, MODEL_LABELS, PLATFORM_LABELS } from '../../strings.js';
import { QueryList } from '../input/QueryList.js';
export function ModelControl() {
@ -16,6 +16,7 @@ export function ModelControl() {
const params = useStore(state, (s) => s.model);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setModel = useStore(state, (s) => s.setModel);
const { t } = useTranslation();
const models = useQuery('models', async () => client.models(), {
staleTime: STALE_TIME,
@ -27,8 +28,8 @@ export function ModelControl() {
return <Stack direction='row' spacing={2}>
<QueryList
id='platforms'
labels={PLATFORM_LABELS}
name='Platform'
labelKey='platform'
name={t('parameter.platform')}
query={{
result: platforms,
}}
@ -41,8 +42,8 @@ export function ModelControl() {
/>
<QueryList
id='diffusion'
labels={MODEL_LABELS}
name='Diffusion Model'
labelKey='model'
name={t('modelType.diffusion')}
query={{
result: models,
selector: (result) => result.diffusion,
@ -56,8 +57,8 @@ export function ModelControl() {
/>
<QueryList
id='inversion'
labels={INVERSION_LABELS}
name='Textual Inversion'
labelKey='model'
name={t('modelType.inversion')}
query={{
result: models,
selector: (result) => result.inversion,
@ -72,8 +73,8 @@ export function ModelControl() {
/>
<QueryList
id='upscaling'
labels={MODEL_LABELS}
name='Upscaling Model'
labelKey='model'
name={t('modelType.upscaling')}
query={{
result: models,
selector: (result) => result.upscaling,
@ -87,8 +88,8 @@ export function ModelControl() {
/>
<QueryList
id='correction'
labels={MODEL_LABELS}
name='Correction Model'
labelKey='model'
name={t('modelType.correction')}
query={{
result: models,
selector: (result) => result.correction,
@ -101,7 +102,7 @@ export function ModelControl() {
}}
/>
<FormControlLabel
label='Long Prompt Weighting'
label={t('parameter.lpw')}
control={<Checkbox
checked={params.lpw}
value='check'

View File

@ -2,6 +2,7 @@ import { mustExist } from '@apextoaster/js-utils';
import { Checkbox, FormControlLabel, Stack } from '@mui/material';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { ConfigContext, StateContext } from '../../state.js';
@ -13,14 +14,15 @@ export function OutpaintControl() {
const outpaint = useStore(state, (s) => s.outpaint);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setOutpaint = useStore(state, (s) => s.setOutpaint);
const { t } = useTranslation();
return <Stack direction='row' spacing={4}>
<FormControlLabel
label='Outpaint'
label={t('parameter.outpaint.label')}
control={<Checkbox
checked={outpaint.enabled}
value='check'
onChange={(event) => {
onChange={(_event) => {
setOutpaint({
enabled: outpaint.enabled === false,
});
@ -28,7 +30,7 @@ export function OutpaintControl() {
/>}
/>
<NumericField
label='Left'
label={t('parameter.outpaint.left')}
disabled={outpaint.enabled === false}
min={0}
max={params.width.max}
@ -41,7 +43,7 @@ export function OutpaintControl() {
}}
/>
<NumericField
label='Right'
label={t('parameter.outpaint.right')}
disabled={outpaint.enabled === false}
min={0}
max={params.width.max}
@ -54,7 +56,7 @@ export function OutpaintControl() {
}}
/>
<NumericField
label='Top'
label={t('parameter.outpaint.top')}
disabled={outpaint.enabled === false}
min={0}
max={params.height.max}
@ -67,7 +69,7 @@ export function OutpaintControl() {
}}
/>
<NumericField
label='Bottom'
label={t('parameter.outpaint.bottom')}
disabled={outpaint.enabled === false}
min={0}
max={params.height.max}

View File

@ -1,8 +1,8 @@
import { mustExist } from '@apextoaster/js-utils';
import { Checkbox, FormControl, FormControlLabel, InputLabel, MenuItem, Select, Stack } from '@mui/material';
import { startCase } from 'lodash';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { ConfigContext, StateContext } from '../../state.js';
@ -14,10 +14,11 @@ export function UpscaleControl() {
const upscale = useStore(state, (s) => s.upscale);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setUpscale = useStore(state, (s) => s.setUpscale);
const { t } = useTranslation();
return <Stack direction='row' spacing={4}>
<FormControlLabel
label='Upscale'
label={t('parameter.upscale.label')}
control={<Checkbox
checked={upscale.enabled}
value='check'
@ -29,7 +30,7 @@ export function UpscaleControl() {
/>}
/>
<NumericField
label='Denoise'
label={t('parameter.upscale.denoise')}
decimal
disabled={upscale.enabled === false}
min={params.denoise.min}
@ -43,7 +44,7 @@ export function UpscaleControl() {
}}
/>
<NumericField
label='Scale'
label={t('parameter.upscale.scale')}
disabled={upscale.enabled === false}
min={params.scale.min}
max={params.scale.max}
@ -56,7 +57,7 @@ export function UpscaleControl() {
}}
/>
<NumericField
label='Outscale'
label={t('parameter.upscale.outscale')}
disabled={upscale.enabled === false}
min={params.outscale.min}
max={params.outscale.max}
@ -69,7 +70,7 @@ export function UpscaleControl() {
}}
/>
<FormControlLabel
label='Face Correction'
label={t('parameter.correction.label')}
control={<Checkbox
checked={upscale.faces}
value='check'
@ -81,7 +82,7 @@ export function UpscaleControl() {
/>}
/>
<NumericField
label='Strength'
label={t('parameter.correction.strength')}
decimal
disabled={upscale.faces === false}
min={params.faceStrength.min}
@ -95,7 +96,7 @@ export function UpscaleControl() {
}}
/>
<NumericField
label='Outscale'
label={t('parameter.correction.outscale')}
disabled={upscale.faces === false}
min={params.faceOutscale.min}
max={params.faceOutscale.max}
@ -111,7 +112,7 @@ export function UpscaleControl() {
<InputLabel id={'upscale-order'}>Upscale Order</InputLabel>
<Select
labelId={'upscale-order'}
label={'Upscale Order'}
label={t('parameter.upscale.order')}
value={upscale.upscaleOrder}
onChange={(e) => {
setUpscale({
@ -119,8 +120,8 @@ export function UpscaleControl() {
});
}}
>
{params.upscaleOrder.keys.map((name) =>
<MenuItem key={name} value={name}>{startCase(name)}</MenuItem>)
{Object.entries(params.upscaleOrder.keys).map(([key, name]) =>
<MenuItem key={key} value={key}>{t(`upscaleOrder.${name}`)}</MenuItem>)
}
</Select>
</FormControl>

View File

@ -2,6 +2,7 @@ import { doesExist, Maybe, mustDefault, mustExist } from '@apextoaster/js-utils'
import { PhotoCamera } from '@mui/icons-material';
import { Button, Stack, Typography } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
export interface ImageInputProps {
filter: string;
@ -14,6 +15,8 @@ export interface ImageInputProps {
}
export function ImageInput(props: ImageInputProps) {
const { t } = useTranslation();
function renderImage() {
if (doesExist(props.image)) {
if (mustDefault(props.hideSelection, false)) {
@ -28,7 +31,7 @@ export function ImageInput(props: ImageInputProps) {
}}
/>;
} else {
return <Typography>Please select an image.</Typography>;
return <Typography>{t('input.image.empty')}</Typography>;
}
}

View File

@ -3,6 +3,7 @@ import { Download, FormatColorFill, Gradient, InvertColors, Save, Undo } from '@
import { Button, Stack, Typography } from '@mui/material';
import { throttle } from 'lodash';
import React, { RefObject, useContext, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { SAVE_TIME } from '../../config.js';
@ -201,6 +202,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
const brush = useStore(state, (s) => s.brush);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setBrush = useStore(state, (s) => s.setBrush);
const { t } = useTranslation();
useEffect(() => {
if (dirty.current) {
@ -308,14 +310,11 @@ export function MaskCanvas(props: MaskCanvasProps) {
onMouseUp={finishPainting}
onMouseMove={drawMouse}
/>
<Typography variant='body1'>
Black pixels in the mask will stay the same, white pixels will be replaced. The masked pixels will be blended
with the noise source before the diffusion model runs, giving it more variety to use.
</Typography>
<Typography variant='body1'>{t('mask.help')}</Typography>
<Stack>
<Stack direction='row' spacing={4}>
<NumericField
label='Brush Color'
label={t('parameter.brush.color')}
min={COLORS.black}
max={COLORS.white}
step={1}
@ -325,7 +324,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
}}
/>
<NumericField
label='Brush Size'
label={t('parameter.brush.size')}
min={1}
max={64}
step={1}
@ -336,7 +335,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
/>
<NumericField
decimal
label='Brush Strength'
label={t('parameter.brush.strength')}
min={0}
max={1}
step={0.01}
@ -353,7 +352,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
onClick={() => {
drawFill(floodBlack);
}}>
Fill with black
{t('mask.fill.black')}
</Button>
<Button
variant='outlined'
@ -361,7 +360,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
onClick={() => {
drawFill(floodWhite);
}}>
Fill with white
{t('mask.fill.white')}
</Button>
<Button
variant='outlined'
@ -369,7 +368,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
onClick={() => {
drawFill(floodInvert);
}}>
Invert
{t('mask.invert')}
</Button>
<Button
variant='outlined'
@ -377,7 +376,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
onClick={() => {
drawFill(floodBelow);
}}>
Gray to black
{t('mask.gray.black')}
</Button>
<Button
variant='outlined'
@ -385,7 +384,7 @@ export function MaskCanvas(props: MaskCanvasProps) {
onClick={() => {
drawFill(floodAbove);
}}>
Gray to white
{t('mask.gray.white')}
</Button>
</Stack>
</Stack>

View File

@ -1,6 +1,7 @@
import { doesExist } from '@apextoaster/js-utils';
import { Slider, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
export function parseNumber(num: string, decimal = false): number {
if (decimal) {
@ -24,14 +25,15 @@ export interface ImageControlProps {
export function NumericField(props: ImageControlProps) {
const { decimal = false, disabled = false, label, min, max, step, value } = props;
const error = (value < min) || (value > max);
const { t } = useTranslation();
return <Stack spacing={2}>
<TextField
error={error}
label={label}
helperText={error && 'Out of range'}
helperText={error && t('input.numeric.error.range')}
disabled={disabled}
variant='outlined'
type='number'

View File

@ -2,6 +2,7 @@ import { doesExist, Maybe } from '@apextoaster/js-utils';
import { TextField } from '@mui/material';
import { Stack } from '@mui/system';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
export interface PromptValue {
prompt: string;
@ -19,18 +20,25 @@ export function PromptInput(props: PromptInputProps) {
const promptLength = prompt.split(' ').length;
const error = promptLength > PROMPT_LIMIT;
const { t } = useTranslation();
function promptHelper() {
const params = {
current: promptLength,
max: PROMPT_LIMIT,
};
if (error) {
return `Too many tokens: ${promptLength}/${PROMPT_LIMIT}`;
return t('input.prompt.error.length', params);
} else {
return `Tokens: ${promptLength}/${PROMPT_LIMIT}`;
return t('input.prompt.tokens', params);
}
}
return <Stack spacing={2}>
<TextField
error={error}
label='Prompt'
label={t('parameter.prompt')}
helperText={promptHelper()}
variant='outlined'
value={prompt}
@ -44,7 +52,7 @@ export function PromptInput(props: PromptInputProps) {
}}
/>
<TextField
label='Negative Prompt'
label={t('parameter.negativePrompt')}
variant='outlined'
value={negativePrompt}
onChange={(event) => {

View File

@ -2,6 +2,7 @@ import { doesExist, mustDefault, mustExist } from '@apextoaster/js-utils';
import { Alert, FormControl, InputLabel, MenuItem, Select, Typography } from '@mui/material';
import * as React from 'react';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { UseQueryResult } from 'react-query';
export interface QueryListComplete {
@ -15,7 +16,7 @@ export interface QueryListFilter<T> {
export interface QueryListProps<T> {
id: string;
labels: Record<string, string>;
labelKey: string;
name: string;
value: string;
@ -47,9 +48,11 @@ export function filterQuery<T>(query: QueryListComplete | QueryListFilter<T>, sh
}
export function QueryList<T>(props: QueryListProps<T>) {
const { labels, query, showEmpty = false, value } = props;
const { labelKey, query, showEmpty = false, value } = props;
const { result } = query;
const { t } = useTranslation();
function firstValidValue(): string {
if (doesExist(value) && data.includes(value)) {
return value;
@ -58,6 +61,10 @@ export function QueryList<T>(props: QueryListProps<T>) {
}
}
function getLabel(name: string) {
return mustDefault(t(`${labelKey}.${name}`), name);
}
// update state when previous selection was invalid: https://github.com/ssube/onnx-web/issues/120
useEffect(() => {
if (result.status === 'success' && doesExist(result.data) && doesExist(props.onChange)) {
@ -70,18 +77,20 @@ export function QueryList<T>(props: QueryListProps<T>) {
if (result.status === 'error') {
if (result.error instanceof Error) {
return <Alert severity='error'>Error: {result.error.message}</Alert>;
return <Alert severity='error'>{t('input.list.error.specific', {
message: result.error.message,
})}</Alert>;
} else {
return <Alert severity='error'>Unknown Error</Alert>;
return <Alert severity='error'>{t('input.list.error.unknown')}</Alert>;
}
}
if (result.status === 'loading') {
return <Typography>Loading...</Typography>;
return <Typography>{t('input.list.loading')}</Typography>;
}
if (result.status === 'idle') {
return <Typography>Idle?</Typography>;
return <Typography>{t('input.list.idle')}</Typography>;
}
// else: success
@ -100,7 +109,7 @@ export function QueryList<T>(props: QueryListProps<T>) {
}
}}
>
{data.map((name) => <MenuItem key={name} value={name}>{mustDefault(labels[name], name)}</MenuItem>)}
{data.map((name) => <MenuItem key={name} value={name}>{getLabel(name)}</MenuItem>)}
</Select>
</FormControl>;
}

View File

@ -2,6 +2,7 @@ import { mustDefault, mustExist } from '@apextoaster/js-utils';
import { Box, Button, Stack } from '@mui/material';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from 'react-query';
import { useStore } from 'zustand';
@ -37,6 +38,7 @@ export function Blend() {
const setBlend = useStore(state, (s) => s.setBlend);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.pushLoading);
const { t } = useTranslation();
const sources = mustDefault(blend.sources, []);
@ -48,7 +50,7 @@ export function Blend() {
filter={IMAGE_FILTER}
image={sources[idx]}
hideSelection={true}
label='Source'
label={t('input.image.source')}
onChange={(file) => {
const newSources = [...sources];
newSources[idx] = file;
@ -73,7 +75,7 @@ export function Blend() {
disabled={sources.length === 0}
variant='contained'
onClick={() => upload.mutate()}
>Generate</Button>
>{t('generate')}</Button>
</Stack>
</Box>;
}

View File

@ -2,6 +2,7 @@ import { doesExist, mustExist } from '@apextoaster/js-utils';
import { Box, Button, Stack } from '@mui/material';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from 'react-query';
import { useStore } from 'zustand';
@ -39,10 +40,11 @@ export function Img2Img() {
const setImg2Img = useStore(state, (s) => s.setImg2Img);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.pushLoading);
const { t } = useTranslation();
return <Box>
<Stack spacing={2}>
<ImageInput filter={IMAGE_FILTER} image={source} label='Source' onChange={(file) => {
<ImageInput filter={IMAGE_FILTER} image={source} label={t('input.image.source')} onChange={(file) => {
setImg2Img({
source: file,
});
@ -50,7 +52,7 @@ export function Img2Img() {
<ImageControl selector={(s) => s.img2img} onChange={setImg2Img} />
<NumericField
decimal
label='Strength'
label={t('parameter.strength')}
min={params.strength.min}
max={params.strength.max}
step={params.strength.step}
@ -66,7 +68,7 @@ export function Img2Img() {
disabled={doesExist(source) === false}
variant='contained'
onClick={() => upload.mutate()}
>Generate</Button>
>{t('generate')}</Button>
</Stack>
</Box>;
}

View File

@ -1,14 +1,13 @@
import { doesExist, mustExist } from '@apextoaster/js-utils';
import { Box, Button, FormControl, FormControlLabel, InputLabel, MenuItem, Select, Stack } from '@mui/material';
import { capitalize } from 'lodash';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQuery, useQueryClient } from 'react-query';
import { useStore } from 'zustand';
import { IMAGE_FILTER, STALE_TIME } from '../../config.js';
import { ClientContext, ConfigContext, StateContext } from '../../state.js';
import { MASK_LABELS, NOISE_LABELS } from '../../strings.js';
import { ImageControl } from '../control/ImageControl.js';
import { OutpaintControl } from '../control/OutpaintControl.js';
import { UpscaleControl } from '../control/UpscaleControl.js';
@ -65,6 +64,7 @@ export function Inpaint() {
const setInpaint = useStore(state, (s) => s.setInpaint);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.pushLoading);
const { t } = useTranslation();
const query = useQueryClient();
const upload = useMutation(uploadSource, {
@ -76,7 +76,7 @@ export function Inpaint() {
<ImageInput
filter={IMAGE_FILTER}
image={source}
label='Source'
label={t('input.image.source')}
hideSelection={true}
onChange={(file) => {
setInpaint({
@ -87,7 +87,7 @@ export function Inpaint() {
<ImageInput
filter={IMAGE_FILTER}
image={mask}
label='Mask'
label={t('input.image.mask')}
hideSelection={true}
onChange={(file) => {
setInpaint({
@ -111,7 +111,7 @@ export function Inpaint() {
}}
/>
<NumericField
label='Strength'
label={t('parameter.strength')}
min={params.strength.min}
max={params.strength.max}
step={params.strength.step}
@ -125,8 +125,9 @@ export function Inpaint() {
<Stack direction='row' spacing={2}>
<QueryList
id='masks'
labels={MASK_LABELS}
name='Mask Filter'
labelKey={'maskFilter'}
showEmpty={true}
name={t('parameter.maskFilter')}
query={{
result: masks,
}}
@ -139,8 +140,8 @@ export function Inpaint() {
/>
<QueryList
id='noises'
labels={NOISE_LABELS}
name='Noise Source'
labelKey={'noiseSource'}
name={t('parameter.noiseSource')}
query={{
result: noises,
}}
@ -155,7 +156,7 @@ export function Inpaint() {
<InputLabel id={'outpaint-tiling'}>Tile Order</InputLabel>
<Select
labelId={'outpaint-tiling'}
label={'Tile Order'}
label={t('parameter.tileOrder')}
value={tileOrder}
onChange={(e) => {
setInpaint({
@ -163,14 +164,14 @@ export function Inpaint() {
});
}}
>
{params.tileOrder.keys.map((name) =>
<MenuItem key={name} value={name}>{capitalize(name)}</MenuItem>)
{Object.entries(params.tileOrder.keys).map(([key, name]) =>
<MenuItem key={key} value={key}>{t(`tileOrder.${name}`)}</MenuItem>)
}
</Select>
</FormControl>
<Stack direction='row' spacing={2}>
<FormControlLabel
label='Fill Color'
label={t('parameter.fillColor')}
sx={{ mx: 1 }}
control={
<input
@ -193,7 +194,7 @@ export function Inpaint() {
disabled={doesExist(source) === false || doesExist(mask) === false}
variant='contained'
onClick={() => upload.mutate()}
>Generate</Button>
>{t('generate')}</Button>
</Stack>
</Box>;
}

View File

@ -3,6 +3,7 @@ import { Refresh } from '@mui/icons-material';
import { Alert, Button, Stack, TextField } from '@mui/material';
import * as React from 'react';
import { useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { getApiRoot } from '../../config.js';
@ -32,28 +33,29 @@ export function Settings() {
const [json, setJson] = useState(JSON.stringify(state, removeBlobs));
const [root, setRoot] = useState(getApiRoot(config));
const { t } = useTranslation();
return <Stack spacing={2}>
<NumericField
label='Image History'
label={t('setting.history')}
min={2}
max={20}
step={1}
value={state.limit}
onChange={(value) => state.setLimit(value)}
/>
<TextField variant='outlined' label='Default Prompt' value={state.defaults.prompt} onChange={(event) => {
<TextField variant='outlined' label={t('setting.prompt')} value={state.defaults.prompt} onChange={(event) => {
state.setDefaults({
prompt: event.target.value,
});
}} />
<TextField variant='outlined' label='Default Scheduler' value={state.defaults.scheduler} onChange={(event) => {
<TextField variant='outlined' label={t('setting.scheduler')} value={state.defaults.scheduler} onChange={(event) => {
state.setDefaults({
scheduler: event.target.value,
});
}} />
<Stack direction='row' spacing={2}>
<TextField variant='outlined' label='API Server' value={root} onChange={(event) => {
<TextField variant='outlined' label={t('setting.server')} value={root} onChange={(event) => {
setRoot(event.target.value);
}} />
<Button variant='contained' startIcon={<Refresh />} onClick={() => {
@ -61,28 +63,28 @@ export function Settings() {
query.set('api', root);
window.location.search = query.toString();
}}>
Connect
{t('setting.connectServer')}
</Button>
<Alert variant='outlined' severity='success'>
{config.params.version}
</Alert>
</Stack>
<Stack direction='row' spacing={2}>
<TextField variant='outlined' label='Client State' value={json} onChange={(event) => {
<TextField variant='outlined' label={t('setting.state')} value={json} onChange={(event) => {
setJson(event.target.value);
}} />
<Button variant='contained' startIcon={<Refresh />} onClick={() => {
window.localStorage.setItem(STATE_KEY, json);
window.location.reload();
}}>
Load
{t('setting.loadState')}
</Button>
</Stack>
<Stack direction='row' spacing={2}>
<Button onClick={() => state.resetTxt2Img()} color='warning'>Reset Txt2Img</Button>
<Button onClick={() => state.resetImg2Img()} color='warning'>Reset Img2Img</Button>
<Button onClick={() => state.resetInpaint()} color='warning'>Reset Inpaint</Button>
<Button onClick={() => state.resetAll()} color='error'>Reset All</Button>
<Button onClick={() => state.resetTxt2Img()} color='warning'>{t('setting.reset.txt2img')}</Button>
<Button onClick={() => state.resetImg2Img()} color='warning'>{t('setting.reset.img2img')}</Button>
<Button onClick={() => state.resetInpaint()} color='warning'>{t('setting.reset.inpaint')}</Button>
<Button onClick={() => state.resetAll()} color='error'>{t('setting.reset.all')}</Button>
</Stack>
</Stack>;
}

View File

@ -2,6 +2,7 @@ import { mustExist } from '@apextoaster/js-utils';
import { Box, Button, Stack } from '@mui/material';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from 'react-query';
import { useStore } from 'zustand';
@ -33,13 +34,14 @@ export function Txt2Img() {
const setTxt2Img = useStore(state, (s) => s.setTxt2Img);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.pushLoading);
const { t } = useTranslation();
return <Box>
<Stack spacing={2}>
<ImageControl selector={(s) => s.txt2img} onChange={setTxt2Img} />
<Stack direction='row' spacing={4}>
<NumericField
label='Width'
label={t('parameter.width')}
min={params.width.min}
max={params.width.max}
step={params.width.step}
@ -51,7 +53,7 @@ export function Txt2Img() {
}}
/>
<NumericField
label='Height'
label={t('parameter.height')}
min={params.height.min}
max={params.height.max}
step={params.height.step}
@ -67,7 +69,7 @@ export function Txt2Img() {
<Button
variant='contained'
onClick={() => generate.mutate()}
>Generate</Button>
>{t('generate')}</Button>
</Stack>
</Box>;
}

View File

@ -2,6 +2,7 @@ import { doesExist, mustExist } from '@apextoaster/js-utils';
import { Box, Button, Stack } from '@mui/material';
import * as React from 'react';
import { useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueryClient } from 'react-query';
import { useStore } from 'zustand';
@ -35,13 +36,14 @@ export function Upscale() {
const setSource = useStore(state, (s) => s.setUpscaleTab);
// eslint-disable-next-line @typescript-eslint/unbound-method
const setLoading = useStore(state, (s) => s.pushLoading);
const { t } = useTranslation();
return <Box>
<Stack spacing={2}>
<ImageInput
filter={IMAGE_FILTER}
image={params.source}
label='Source'
label={t('input.image.source')}
onChange={(file) => {
setSource({
source: file,
@ -60,7 +62,7 @@ export function Upscale() {
disabled={doesExist(params.source) === false}
variant='contained'
onClick={() => upload.mutate()}
>Generate</Button>
>{t('generate')}</Button>
</Stack>
</Box>;
}

View File

@ -11,7 +11,7 @@ export interface ConfigNumber {
export interface ConfigString {
default: string;
keys: Array<string>;
keys: Record<string, string>;
}
/**

View File

@ -61,8 +61,11 @@ export async function main() {
escapeValue: false, // not needed for react as it escapes by default
},
resources: I18N_STRINGS,
returnEmptyString: false,
});
i18n.addResourceBundle(i18n.resolvedLanguage, 'model', params.model.keys);
// prep zustand with a slice for each tab, using local storage
const {
createBrushSlice,

View File

@ -1,84 +0,0 @@
// TODO: set up i18next
export const MODEL_LABELS: Record<string, string> = {
'stable-diffusion-onnx-v1-4': 'Stable Diffusion v1.4',
'stable-diffusion-onnx-v1-5': 'Stable Diffusion v1.5',
'stable-diffusion-onnx-v1-inpainting': 'SD Inpainting v1',
'stable-diffusion-onnx-v2-0': 'Stable Diffusion v2.0',
'stable-diffusion-onnx-v2-1': 'Stable Diffusion v2.1',
'stable-diffusion-onnx-v2-inpainting': 'SD Inpainting v2',
// upscaling
'upscaling-real-esrgan-x2-plus': 'Real ESRGAN x2 Plus',
'upscaling-real-esrgan-x4-plus': 'Real ESRGAN x4 Plus',
'upscaling-real-esrgan-x4-v3': 'Real ESRGAN x4 v3',
'upscaling-stable-diffusion-x4': 'Stable Diffusion x4',
// correction
'correction-codeformer': 'CodeFormer',
'correction-gfpgan-v1-3': 'GFPGAN v1.3',
// extras
'diffusion-stablydiffused-aesthetic-v2-6': 'Aesthetic Mix v2.6',
'diffusion-anything': 'Anything',
'diffusion-anything-v3': 'Anything v3',
'diffusion-anything-v4': 'Anything v4',
'diffusion-darkvictorian': 'Dark Victorian',
'diffusion-dreamlike-photoreal': 'Dreamlike Photoreal',
'diffusion-dreamlike-photoreal-v1': 'Dreamlike Photoreal 1.0',
'diffusion-dreamlike-photoreal-v2': 'Dreamlike Photoreal 2.0',
'diffusion-ghibli': 'Ghibli',
'diffusion-knollingcase': 'Knollingcase',
'diffusion-openjourney': 'OpenJourney',
'diffusion-openjourney-v1': 'OpenJourney v1',
'diffusion-openjourney-v2': 'OpenJourney v2',
'diffusion-pastel-mix': 'Pastel Mix',
'diffusion-unstable-ink-dream-v6': 'Unstable Ink Dream v6',
};
export const INVERSION_LABELS: Record<string, string> = {
'': 'None',
'inversion-cubex': 'Cubex',
'inversion-birb': 'Birb Style',
'inversion-line-art': 'Line Art',
'inversion-minecraft': 'Minecraft Concept',
};
export const PLATFORM_LABELS: Record<string, string> = {
amd: 'AMD GPU',
// eslint-disable-next-line id-blacklist
any: 'Any Platform',
cpu: 'CPU',
cuda: 'CUDA',
directml: 'DirectML',
nvidia: 'Nvidia GPU',
rocm: 'ROCm',
};
export const SCHEDULER_LABELS: Record<string, string> = {
'ddim': 'DDIM',
'ddpm': 'DDPM',
'deis-multi': 'DEIS Multistep',
'dpm-multi': 'DPM Multistep',
'dpm-single': 'DPM Singlestep',
'euler': 'Euler',
'euler-a': 'Euler Ancestral',
'heun': 'Heun',
'k-dpm-2-a': 'KDPM2 Ancestral',
'k-dpm-2': 'KDPM2',
'karras-ve': 'Karras Ve',
'ipndm': 'iPNDM',
'lms-discrete': 'LMS',
'pndm': 'PNDM',
};
export const NOISE_LABELS: Record<string, string> = {
'fill-edge': 'Fill Edges',
'fill-mask': 'Fill Masked',
'gaussian': 'Gaussian Blur',
'histogram': 'Histogram Noise',
'normal': 'Gaussian Noise',
'uniform': 'Uniform Noise',
};
export const MASK_LABELS: Record<string, string> = {
'none': 'None',
'gaussian-multiply': 'Gaussian Multiply',
'gaussian-screen': 'Gaussian Screen',
};

View File

@ -1,7 +1,18 @@
import { I18N_STRINGS_EN, RequiredStrings } from './en.js';
import { I18N_STRINGS_DE } from './de.js';
import { I18N_STRINGS_EN } from './en.js';
import { I18N_STRINGS_ES } from './es.js';
import { I18N_STRINGS_FR } from './fr.js';
export const I18N_STRINGS: Record<string, RequiredStrings> = {
// easy way to make sure all locales have the complete set of strings
export type RequiredStrings = typeof I18N_STRINGS_EN['en']['translation'];
interface PartialLanguage {
[key: string]: Omit<RequiredStrings, 'model' | 'platform' | 'scheduler'>;
}
export const I18N_STRINGS: Record<string, PartialLanguage> = {
...I18N_STRINGS_DE,
...I18N_STRINGS_EN,
...I18N_STRINGS_ES,
...I18N_STRINGS_FR,
};

159
gui/src/strings/de.ts Normal file
View File

@ -0,0 +1,159 @@
/**
* This is a machine translation and may have some mistakes.
*
* If you have a more accurate translation for any of these strings, please open an issue or pull request.
*/
export const I18N_STRINGS_DE = {
de: {
translation: {
generate: 'Erzeugen',
history: {
empty: 'Keine neuere Geschichte. Drücken Sie Generieren, um ein Bild zu erstellen.',
},
input: {
image: {
empty: 'Bitte wählen Sie ein Bild aus',
mask: 'Maskenbild',
source: 'Quellbild',
},
list: {
error: {
specific: '',
unknown: 'unbekannter Fehler',
},
idle: '',
loading: '',
},
numeric: {
error: {
range: 'außerhalb der Reichweite',
},
},
prompt: {
tokens: '',
error: {
length: '',
},
},
},
loading: {
cancel: 'stornieren',
progress: '{{current}} von {{total}} Schritten',
unknown: 'vielen',
},
mask: {
fill: {
black: 'Mit Schwarz füllen',
white: 'Weiß füllen',
},
gray: {
black: 'Grau in schwarz umwandeln',
white: 'Grau in weiß umwandeln',
},
help: '',
invert: 'Farben umkehren',
},
maskFilter: {
'gaussian-multiply': '',
'gaussian-screen': '',
},
modelType: {
correction: 'Korrekturmodelle',
diffusion: 'Diffusionsmodelle',
inversion: '',
upscaling: 'Modelle vergrößern',
},
noiseSource: {
'fill-edge': 'Kanten füllen',
'fill-mask': 'Maskiert füllen',
'gaussian': 'Gaußsche Unschärfe',
'histogram': 'Histogrammrauschen',
'normal': 'Gaußsches Rauschen',
'uniform': 'gleichförmiges Rauschen',
},
parameter: {
batch: 'Losgröße',
brush: {
color: 'Pinselfarbe',
size: 'Pinselgröße',
strength: 'Pinseldeckkraft',
},
cfg: '',
eta: '',
fillColor: 'Füllfarbe',
height: 'Höhe',
lpw: '',
maskFilter: 'Maskenfilter',
noiseSource: 'Lärmquelle',
negativePrompt: 'Gegenprompt',
newSeed: 'neue Saat',
outpaint: {
label: '',
left: 'Links',
right: 'Rechts',
top: 'Top',
bottom: 'Unterseite',
},
platform: '',
prompt: 'Prompt',
scheduler: 'Planer',
seed: 'Saat',
size: '',
steps: 'Schritte',
strength: 'Stärke',
tileOrder: '',
upscale: {
label: '',
denoise: 'Entrauschen',
scale: 'Skala',
order: '',
outscale: 'Ausgangsskala',
},
width: 'Breite',
correction: {
label: 'Gesichtskorrektur',
strength: 'Stärke',
outscale: 'Ausgangsskala',
},
},
setting: {
connectServer: 'verbinden zum Server',
history: 'Bildgeschichte',
loadState: 'Laden',
prompt: 'Standard-Eingabeaufforderung',
reset: {
all: 'Alles zurücksetzen',
img2img: 'Img2img zurücksetzen',
inpaint: 'Inpaint zurücksetzen',
txt2img: 'Txt2img zurücksetzen',
},
scheduler: 'Standardplaner',
server: 'API-Server',
state: 'Kundenstatus',
},
tab: {
blend: 'Mischung',
img2img: '',
inpaint: '',
txt2txt: '',
txt2img: '',
upscale: 'Vergrößern',
},
tileOrder: {
grid: 'Raster',
spiral: 'Spiral',
},
tooltip: {
delete: 'Löschen',
next: 'Nächste',
previous: 'Vorherige',
save: 'Speichern',
},
upscaleOrder: {
'correction-both': '',
'correction-first': '',
'correction-last': '',
},
},
},
};

View File

@ -1,14 +1,198 @@
export const I18N_STRINGS_EN = {
en: {
translation: {
generate: 'Generate',
history: {
empty: 'No results. Press Generate to create an image.',
empty: 'No recent history. Press Generate to create an image.',
},
input: {
image: {
empty: 'Please select an image.',
mask: 'Mask',
source: 'Source',
},
list: {
error: {
specific: 'Error: {{message}}',
unknown: 'Unknown Error',
},
idle: 'Idle?',
loading: 'Loading...',
},
numeric: {
error: {
range: 'Out of range',
},
},
prompt: {
tokens: 'Tokens: {{current}}/{{max}}',
error: {
length: 'Too many tokens: {{current}}/{{max}}',
},
},
},
loading: {
cancel: 'Cancel',
progress: '{{current}} of {{total}} steps',
unknown: 'many',
},
mask: {
fill: {
black: 'Fill with black',
white: 'Fill with white',
},
gray: {
black: 'Gray to black',
white: 'Gray to white',
},
// eslint-disable-next-line max-len
help: 'Black pixels in the mask will stay the same, white pixels will be replaced. The masked pixels will be blended with the noise source before the diffusion model runs, giving it more variety to use.',
invert: 'Invert',
},
maskFilter: {
'gaussian-multiply': 'Gaussian Multiply',
'gaussian-screen': 'Gaussian Screen',
},
model: {
'': 'None',
// correction
'correction-codeformer': 'CodeFormer',
'correction-gfpgan-v1-3': 'GFPGAN v1.3',
// diffusion
'stable-diffusion-onnx-v1-4': 'Stable Diffusion v1.4',
'stable-diffusion-onnx-v1-5': 'Stable Diffusion v1.5',
'stable-diffusion-onnx-v1-inpainting': 'SD Inpainting v1',
'stable-diffusion-onnx-v2-0': 'Stable Diffusion v2.0',
'stable-diffusion-onnx-v2-1': 'Stable Diffusion v2.1',
'stable-diffusion-onnx-v2-inpainting': 'SD Inpainting v2',
// inversion
'inversion-cubex': 'Cubex',
'inversion-birb': 'Birb Style',
'inversion-line-art': 'Line Art',
'inversion-minecraft': 'Minecraft Concept',
'inversion-ugly-sonic': 'Ugly Sonic',
// upscaling
'upscaling-real-esrgan-x2-plus': 'Real ESRGAN x2 Plus',
'upscaling-real-esrgan-x4-plus': 'Real ESRGAN x4 Plus',
'upscaling-real-esrgan-x4-v3': 'Real ESRGAN x4 v3',
'upscaling-stable-diffusion-x4': 'Stable Diffusion x4',
// extras
'diffusion-stablydiffused-aesthetic-v2-6': 'Aesthetic Mix v2.6',
'diffusion-anything': 'Anything',
'diffusion-anything-v3': 'Anything v3',
'diffusion-anything-v4': 'Anything v4',
'diffusion-darkvictorian': 'Dark Victorian',
'diffusion-dreamlike-photoreal': 'Dreamlike Photoreal',
'diffusion-dreamlike-photoreal-v1': 'Dreamlike Photoreal 1.0',
'diffusion-dreamlike-photoreal-v2': 'Dreamlike Photoreal 2.0',
'diffusion-ghibli': 'Ghibli',
'diffusion-knollingcase': 'Knollingcase',
'diffusion-openjourney': 'OpenJourney',
'diffusion-openjourney-v1': 'OpenJourney v1',
'diffusion-openjourney-v2': 'OpenJourney v2',
'diffusion-pastel-mix': 'Pastel Mix',
'diffusion-unstable-ink-dream-v6': 'Unstable Ink Dream v6',
},
modelType: {
correction: 'Correction Model',
diffusion: 'Diffusion Model',
inversion: 'Textual Inversion',
upscaling: 'Upscaling Model',
},
noiseSource: {
'fill-edge': 'Fill Edges',
'fill-mask': 'Fill Masked',
'gaussian': 'Gaussian Blur',
'histogram': 'Histogram Noise',
'normal': 'Gaussian Noise',
'uniform': 'Uniform Noise',
},
parameter: {
batch: 'Batch Size',
brush: {
color: 'Brush Color',
size: 'Brush Size',
strength: 'Brush Strength',
},
cfg: 'CFG',
eta: 'Eta',
fillColor: 'Fill Color',
height: 'Height',
lpw: 'Long Prompt Weighting',
maskFilter: 'Mask Filter',
noiseSource: 'Noise Source',
negativePrompt: 'Negative Prompt',
newSeed: 'New Seed',
outpaint: {
label: 'Outpaint',
left: 'Left',
right: 'Right',
top: 'Top',
bottom: 'Bottom',
},
platform: 'Platform',
prompt: 'Prompt',
scheduler: 'Scheduler',
seed: 'Seed',
size: 'Size',
steps: 'Steps',
strength: 'Strength',
tileOrder: 'Tile Order',
upscale: {
label: 'Upscale',
denoise: 'Denoise',
scale: 'Scale',
order: 'Upscale Order',
outscale: 'Outscale',
},
width: 'Width',
correction: {
label: 'Face Correction',
strength: 'Strength',
outscale: 'Outscale',
},
},
platform: {
amd: 'AMD GPU',
// eslint-disable-next-line id-blacklist
any: 'Any Platform',
cpu: 'CPU',
cuda: 'CUDA',
directml: 'DirectML',
nvidia: 'Nvidia GPU',
rocm: 'ROCm',
},
setting: {
connectServer: 'Connect',
history: 'Image History',
loadState: 'Load',
prompt: 'Default Prompt',
reset: {
all: 'Reset All',
img2img: 'Reset Img2img',
inpaint: 'Reset Inpaint',
txt2img: 'Reset Txt2img',
},
scheduler: 'Default Scheduler',
server: 'API Server',
state: 'Client State',
},
scheduler: {
'ddim': 'DDIM',
'ddpm': 'DDPM',
'deis-multi': 'DEIS Multistep',
'dpm-multi': 'DPM Multistep',
'dpm-single': 'DPM Singlestep',
'euler': 'Euler',
'euler-a': 'Euler Ancestral',
'heun': 'Heun',
'k-dpm-2-a': 'KDPM2 Ancestral',
'k-dpm-2': 'KDPM2',
'karras-ve': 'Karras Ve',
'ipndm': 'iPNDM',
'lms-discrete': 'LMS',
'pndm': 'PNDM',
},
tab: {
blend: 'Blend',
img2img: 'Img2img',
@ -17,15 +201,21 @@ export const I18N_STRINGS_EN = {
txt2img: 'Txt2img',
upscale: 'Upscale',
},
tileOrder: {
grid: 'Grid',
spiral: 'Spiral',
},
tooltip: {
delete: 'Delete',
next: 'EN Next',
previous: 'EN Previous',
next: 'Next',
previous: 'Previous',
save: 'Save',
},
upscaleOrder: {
'correction-both': 'Correction Both',
'correction-first': 'Correction First',
'correction-last': 'Correction Last',
},
}
},
};
// easy way to make sure all locales have the complete set of strings
export type RequiredStrings = typeof I18N_STRINGS_EN['en'];

159
gui/src/strings/es.ts Normal file
View File

@ -0,0 +1,159 @@
/**
* This is a machine translation and may have some mistakes.
*
* If you have a more accurate translation for any of these strings, please open an issue or pull request.
*/
export const I18N_STRINGS_ES = {
es: {
translation: {
generate: 'Generar',
history: {
empty: 'Sin antecedentes recientes. Presiona generar para crear una nueva imagen.',
},
input: {
image: {
empty: 'Por favor, seleccione una imagen.',
mask: 'Máscara de imagen',
source: 'Imagen de origen',
},
list: {
error: {
specific: 'Error: {{message}}',
unknown: 'Error desconocido',
},
idle: '',
loading: '',
},
numeric: {
error: {
range: 'Fuera de intervalo',
},
},
prompt: {
tokens: '',
error: {
length: '',
},
},
},
loading: {
cancel: 'Cancelar',
progress: '{{current}} de {{total}} pasos',
unknown: 'muchos',
},
mask: {
fill: {
black: 'Llenar con negro',
white: 'Llenar con blanco',
},
gray: {
black: 'Convertir gris a negro',
white: 'Convertir gris a blanco',
},
help: '',
invert: 'Colores invertidos',
},
maskFilter: {
'gaussian-multiply': '',
'gaussian-screen': '',
},
modelType: {
correction: 'Modelo de corrección',
diffusion: 'Modelo de difusión',
inversion: '',
upscaling: 'Modelo de aumento',
},
noiseSource: {
'fill-edge': 'Rellena los bordes',
'fill-mask': 'Rellena la máscara',
'gaussian': 'Desenfoque gaussiano',
'histogram': 'Ruido de histograma',
'normal': 'Ruido gaussiano',
'uniform': 'Ruido uniforme',
},
parameter: {
batch: 'Tamaño del lote',
brush: {
color: 'Color del pincel',
size: 'Tamaño del pincel',
strength: 'Opacidad del pincel',
},
cfg: '',
eta: '',
fillColor: 'Color de relleno',
height: 'Altura',
lpw: '',
maskFilter: 'Filtro de máscara',
noiseSource: 'Fuente de ruido',
negativePrompt: '',
newSeed: '',
outpaint: {
label: '',
left: 'Izquierda',
right: 'Derecha',
top: 'Top',
bottom: 'Fondo',
},
platform: 'Plataforma de hardware',
prompt: 'Aviso',
scheduler: 'Planificador',
seed: 'Semilla',
size: '',
steps: 'Pasos',
strength: 'Fuerza',
tileOrder: 'Orden de secciones',
upscale: {
label: 'Aumento',
denoise: '',
scale: 'Escala',
order: '',
outscale: 'Escala de producción',
},
width: 'Anchura',
correction: {
label: 'Corrección facial',
strength: 'Fuerza',
outscale: 'Escala de producción',
},
},
setting: {
connectServer: 'Conectar al servidor',
history: 'Historia de la imagen',
loadState: '',
prompt: '',
reset: {
all: 'Resetear todo',
img2img: 'Resetear img2img',
inpaint: 'Resetear inpaint',
txt2img: 'Resetear txt2img',
},
scheduler: 'Programador predeterminado',
server: 'Servidor API',
state: 'Estado del cliente',
},
tab: {
blend: 'Mezclar',
img2img: 'Img2img',
inpaint: 'Inpaint',
txt2txt: 'Txt2txt',
txt2img: 'Txt2img',
upscale: 'Aumentar',
},
tileOrder: {
grid: 'Red',
spiral: 'Espiral',
},
tooltip: {
delete: 'Borrar',
next: 'Próximo',
previous: 'Anterior',
save: 'Ahorrar',
},
upscaleOrder: {
'correction-both': '',
'correction-first': '',
'correction-last': '',
},
},
},
};

View File

@ -1,20 +1,159 @@
/**
* This is a machine translation and may have some mistakes.
*
* If you have a more accurate translation for any of these strings, please open an issue or pull request.
*/
export const I18N_STRINGS_FR = {
fr: {
translation: {
generate: '',
history: {
empty: '',
},
input: {
image: {
empty: '',
mask: '',
source: '',
},
list: {
error: {
specific: '',
unknown: '',
},
idle: '',
loading: '',
},
numeric: {
error: {
range: '',
},
},
prompt: {
tokens: '',
error: {
length: '',
},
},
},
loading: {
cancel: '',
progress: '',
unknown: '',
},
mask: {
fill: {
black: '',
white: '',
},
gray: {
black: '',
white: '',
},
help: '',
invert: '',
},
maskFilter: {
'gaussian-multiply': '',
'gaussian-screen': '',
},
modelType: {
correction: '',
diffusion: '',
inversion: '',
upscaling: '',
},
noiseSource: {
'fill-edge': '',
'fill-mask': '',
'gaussian': '',
'histogram': '',
'normal': '',
'uniform': '',
},
parameter: {
batch: '',
brush: {
color: '',
size: '',
strength: '',
},
cfg: '',
eta: '',
fillColor: '',
height: '',
lpw: '',
maskFilter: '',
noiseSource: '',
negativePrompt: '',
newSeed: '',
outpaint: {
label: '',
left: '',
right: '',
top: '',
bottom: '',
},
platform: '',
prompt: '',
scheduler: '',
seed: '',
size: '',
steps: '',
strength: '',
tileOrder: '',
upscale: {
label: '',
denoise: '',
scale: '',
order: '',
outscale: '',
},
width: '',
correction: {
label: '',
strength: '',
outscale: '',
},
},
setting: {
connectServer: '',
history: '',
loadState: '',
prompt: '',
reset: {
all: '',
img2img: '',
inpaint: '',
txt2img: '',
},
scheduler: '',
server: '',
state: '',
},
tab: {
blend: 'Blend',
img2img: 'Img2img',
inpaint: 'Inpaint',
txt2txt: 'Txt2txt',
txt2img: 'Txt2img',
upscale: 'Upscale',
blend: '',
img2img: '',
inpaint: '',
txt2txt: '',
txt2img: '',
upscale: '',
},
tileOrder: {
grid: '',
spiral: '',
},
tooltip: {
delete: 'Delete',
next: 'FR-Next',
previous: 'FR-Previous',
save: 'Save',
delete: '',
next: '',
previous: '',
save: '',
},
}
upscaleOrder: {
'correction-both': '',
'correction-first': '',
'correction-last': '',
},
},
},
};