feat: 完成前端汉化

This commit is contained in:
cp3hnu 2024-04-29 17:26:10 +08:00
parent 84418b4832
commit f62c219725
43 changed files with 104631 additions and 5994 deletions

1
.gitignore vendored
View File

@ -34,7 +34,6 @@ label_studio/core/static_build
label_studio/core/version_.py
label_studio/core/core
label_studio/tests/test_data/tasks_and_annotations.json
label_studio/frontend/dist/react-app
archive

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,122 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/**
* chroma.js - JavaScript library for color conversions
*
* Copyright (c) 2011-2019, Gregor Aisch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name Gregor Aisch may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* -------------------------------------------------------
*
* chroma.js includes colors from colorbrewer2.org, which are released under
* the following license:
*
* Copyright (c) 2002 Cynthia Brewer, Mark Harrower,
* and The Pennsylvania State University.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*
* ------------------------------------------------------
*
* Named colors are taken from X11 Color Names.
* http://www.w3.org/TR/css3-color/#svg-color
*
* @preserve
*/
/** @license React v0.20.1
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.1
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.1
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.1
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -31,9 +31,9 @@ const loadAsyncPage = async (url) => {
modal({
body: () => (
<ErrorWrapper
title={`Error ${response.status}: ${response.statusText}`}
title={`错误 ${response.status}${response.statusText}`}
errorId={response.status}
stacktrace={`Cannot load url ${url}\n\n${html}`}
stacktrace={`无法加载 URL ${url}\n\n${html}`}
/>
),
allowClose: false,
@ -50,8 +50,8 @@ const loadAsyncPage = async (url) => {
body: () => (
<ErrorWrapper
possum={false}
title={"Connection refused"}
message={"Server not responding. Is it still running?"}
title={"无法连接"}
message={"服务器无响应"}
/>
),
allowClose: true,

View File

@ -39,9 +39,9 @@ export default class ErrorBoundary extends Component {
<Modal onHide={() => location.reload()} style={{ width: "60vw" }} visible bare>
<div style={{padding: 40}}>
<ErrorWrapper
title="Runtime error"
title="运行时错误"
message={error}
stacktrace={`${errorInfo ? `Component Stack: ${errorInfo}` : ''}\n\n${this.state.error?.stack ?? ''}`}
stacktrace={`${errorInfo ? `错误信息:${errorInfo}` : ''}\n\n${this.state.error?.stack ?? ''}`}
onGoBack={goBack}
onReload={() => location.reload()}
/>

View File

@ -82,14 +82,15 @@ export const ErrorWrapper = ({title, message, errorId, stacktrace, validation, v
<Space size="small">
{preparedStackTrace && <Button disabled={copied} onClick={copyStacktrace} style={{width: 180}}>
{copied ? "Copied" : "Copy Stacktrace"}
{copied ? "已复制" : "复制堆栈信息"}
</Button>}
{onGoBack && <Button onClick={onGoBack}>Go Back</Button>}
{onReload && <Button onClick={onReload}>Reload</Button>}
{onGoBack && <Button onClick={onGoBack}>返回</Button>}
{onReload && <Button onClick={onReload}>重新加载</Button>}
</Space>
</Space>
</Elem>
</Block>
);
};

View File

@ -20,7 +20,7 @@ const Select = ({label, className, options, validate, required, skip, labelProps
>
{ref => (
<div className={classList}>
<select {...props} ref={ref} className={rootClass.elem('list')}>
<select {...props} ref={ref} className={rootClass.elem('list')} required>
{props.placeholder && (
<option value="" disabled hidden selected>{props.placeholder}</option>
)}

View File

@ -1,6 +1,8 @@
.select
position relative
&__list
top 0
left 0
@ -16,6 +18,9 @@
font-size: inherit
border-right 10px solid transparent
&:invalid
color: gray !important;
&_ghost
margin-left -10px
border 1px solid transparent

View File

@ -496,7 +496,7 @@ Form.Indicator = () => {
return (
<Block name="form-indicator">
<Oneof value={state}>
<Elem tag="span" mod={{type: state}} name="item" case="success">Saved!</Elem>
<Elem tag="span" mod={{type: state}} name="item" case="success">保存!</Elem>
</Oneof>
</Block>
);

View File

@ -4,7 +4,7 @@ import './Validation.styl';
export const required = (fieldName, value) => {
if (!isDefined(value) || isEmptyString(value)) {
return `${fieldName} is required`;
return `${fieldName} 必填`;
}
};
@ -12,12 +12,12 @@ export const matchPattern = (pattern) => (fieldName, value) => {
pattern = (typeof pattern === 'string') ? new RegExp(pattern) : pattern;
if (!isEmptyString(value) && value.match(pattern) === null) {
return `${fieldName} must match the pattern ${pattern}`;
return `${fieldName} 必须匹配模式 ${pattern}`;
}
};
export const json = (fieldName, value) => {
const err = `${fieldName} must be valid JSON string`;
const err = `${fieldName} 必须是有效的 JSON 字符串`;
if (!isDefined(value) || value.trim().length === 0) return;
@ -36,6 +36,6 @@ export const regexp = (fieldName, value) => {
try {
new RegExp(value);
} catch (err) {
return `${fieldName} must be a valid regular expression`;
return `${fieldName} 必须是一个有效的正则表达式`;
}
};

View File

@ -137,14 +137,14 @@ export const Menubar = ({
<Menu>
<Menu.Item
icon={<LsSettings/>}
label="Account & Settings"
label="帐户与设置"
href="/user/account"
data-external
/>
{/* <Menu.Item label="Dark Mode"/> */}
<Menu.Item
icon={<LsDoor/>}
label="Log Out"
label="退出登录"
href={absoluteURL("/logout")}
data-external
/>
@ -170,7 +170,7 @@ export const Menubar = ({
>
<Menu>
<Menu.Item
label="Projects"
label="项目"
to="/projects"
icon={<IconFolder/>}
data-external
@ -223,7 +223,7 @@ export const Menubar = ({
onClick={sidebarPin}
active={sidebarPinned}
>
{sidebarPinned ? "Unpin menu" : "Pin menu"}
{sidebarPinned ? "取消固定菜单" : "固定菜单"}
</Menu.Item>
</Menu>

View File

@ -70,7 +70,7 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro
size="compact"
autoFocus
>
{cancelText ?? "Cancel"}
{cancelText ?? "取消"}
</Button>
<Button
@ -81,7 +81,7 @@ export const confirm = ({ okText, onOk, cancelText, onCancel, buttonLook, ...pro
size="compact"
look={buttonLook ?? 'primary'}
>
{okText ?? "OK"}
{okText ?? "确定"}
</Button>
</Space>
),
@ -103,7 +103,7 @@ export const info = ({ okText, onOkPress, ...props }) => {
look="primary"
size="compact"
>
{okText ?? "OK"}
{okText ?? "确定"}
</Button>
</Space>
),

View File

@ -38,14 +38,15 @@ const configClass = cn("configure");
const EmptyConfigPlaceholder = () => (
<div className={configClass.elem("empty-config")}>
<p>Your labeling configuration is empty. It is required to label your data.</p>
<p>您的标注配置是空的它是必须的来标注您的数据</p>
<p>
Start from one of our predefined templates or create your own config on the Code panel.
The labeling config is XML-based and you can <a href="https://labelstud.io/tags/" target="_blank">read about the available tags in our documentation</a>.
从我们的预定义模板开始或在代码面板上创建您自己的配置
标注配置是基于 XML 您可以<a href="https://labelstud.io/tags/" target="_blank">在我们的文档中阅读可用的标签</a>
</p>
</div>
);
const Label = ({ label, template, color }) => {
const value = label.getAttribute("value");
@ -91,12 +92,12 @@ const ConfigureControl = ({ control, template }) => {
return (
<div className={configClass.elem("labels")}>
<form className={configClass.elem("add-labels")} action="">
<h4>{tagname === "Choices" ? "Add choices" : "Add label names"}</h4>
<h4>{tagname === "Choices" ? "添加选择项" : "添加标签名"}</h4>
<textarea name="labels" id="" cols="30" rows="5" ref={refLabels} onKeyPress={onKeyPress}></textarea>
<input type="button" value="Add" onClick={onAddLabels} />
<input type="button" value="添加" onClick={onAddLabels} />
</form>
<div className={configClass.elem("current-labels")}>
<h3>{tagname === "Choices" ? "Choices" : "Labels"} ({control.children.length})</h3>
<h3>{tagname === "Choices" ? "选择项" : "标签"} ({control.children.length})</h3>
<ul>
{Array.from(control.children).map(label => (
<Label
@ -181,7 +182,7 @@ const ConfigureSettings = ({ template }) => {
return (
<ul className={configClass.elem("settings")}>
<li>
<h4>Configure settings</h4>
<h4>配置设置</h4>
<ul className={configClass.elem("object-settings")}>
{items}
</ul>
@ -201,29 +202,29 @@ const ConfigureColumns = ({ columns, template }) => {
return (
<div className={configClass.elem("object")}>
<h4>Configure data</h4>
<h4>配置数据</h4>
{template.objects.length > 1 && columns?.length > 0 && columns.length < template.objects.length && (
<p className={configClass.elem("object-error")}>This template requires more data then you have for now</p>
<p className={configClass.elem("object-error")}>这个模板需要更多的数据当前您只有{columns.length}个数据</p>
)}
{columns?.length === 0 && (
<p className={configClass.elem("object-error")}>
To select which field(s) to label you need to upload the data. Alternatively, you can provide it using Code mode.
为了选择哪些字段进行标注您需要上传数据或者您可以使用代码模式
</p>
)}
{template.objects.map(obj => (
<p key={obj.getAttribute("name")}>
Use {obj.tagName.toLowerCase()}
{template.objects > 1 && ` for ${obj.getAttribute("name")}`}
{" from "}
{columns?.length > 0 && columns[0] !== DEFAULT_COLUMN && "field "}
使用 {obj.tagName.toLowerCase()}
{template.objects > 1 && ` 用于 ${obj.getAttribute("name")}`}
{" "}
{columns?.length > 0 && columns[0] !== DEFAULT_COLUMN && "字段 "}
<select onChange={updateValue(obj)} value={obj.getAttribute("value")?.replace(/^\$/, "")}>
{columns?.map(column => (
<option key={column} value={column}>
{column === DEFAULT_COLUMN ? "<imported file>" : `$${column}`}
{column === DEFAULT_COLUMN ? "<导入的文件>" : `$${column}`}
</option>
))}
{!columns?.length && (
<option value={obj.getAttribute("value")?.replace(/^\$/, "")}>{"<imported file>"}</option>
<option value={obj.getAttribute("value")?.replace(/^\$/, "")}>{"<导入的文件>"}</option>
)}
</select>
</p>
@ -302,19 +303,20 @@ const Configurator = ({ columns, config, project, template, setTemplate, onBrows
const extra = (
<p className={configClass.elem('tags-link')}>
Configure the labeling interface with tags.
使用标签来配置标注界面
<br/>
<a href="https://labelstud.io/tags/" target="_blank">See all available tags</a>
<a href="https://labelstud.io/tags/" target="_blank">查看所有可用标签</a>
.
</p>
);
return (
<div className={configClass}>
<div className={configClass.elem("container")}>
<header>
<button onClick={onBrowse}>Browse Templates</button>
<ToggleItems items={{ code: "Code", visual: "Visual" }} active={configure} onSelect={onSelect} />
<button onClick={onBrowse}>浏览模板</button>
<ToggleItems items={{ code: "代码", visual: "可视化" }} active={configure} onSelect={onSelect} />
</header>
<div className={configClass.elem('editor')}>
{configure === "code" && (
@ -341,7 +343,7 @@ const Configurator = ({ columns, config, project, template, setTemplate, onBrows
{disableSaveButton !== true && onSaveClick && (
<Form.Actions size="small" extra={configure === "code" && extra} valid>
<Button look="primary" size="compact" style={{width: 120}} onClick={onSave} waiting={waiting}>
Save
保存
</Button>
</Form.Actions>
)}

View File

@ -47,7 +47,7 @@ export const Preview = ({ config, data, error }) => {
return (
<div className={configClass.elem("preview")}>
<h3>UI Preview</h3>
<h3>UI 预览</h3>
{error && <div className={configClass.elem("preview-error")}>
<h2>{error.detail} {error.id}</h2>
{error.validation_errors?.non_field_errors?.map?.(err => <p key={err}>{err}</p>)}

View File

@ -70,7 +70,7 @@ export const TemplatesList = ({ selectedGroup, selectedRecipe, onCustomTemplate,
</li>
))}
</ul>
<button type="button" onClick={onCustomTemplate} className={listClass.elem("custom-template")}>Custom template</button>
<button type="button" onClick={onCustomTemplate} className={listClass.elem("custom-template")}>自定义模板</button>
</aside>
<main>
{!templates && <Spinner style={{ width: "100%", height: 200 }} />}
@ -78,7 +78,7 @@ export const TemplatesList = ({ selectedGroup, selectedRecipe, onCustomTemplate,
</main>
<footer>
<IconInfo className={listClass.elem("info-icon")} width="20" height="20" />
See the documentation to <a href="https://labelstud.io/guide" target="_blank">contribute a template</a>.
想要了解如何贡献模板请查看<a href="https://labelstud.io/guide" target="_blank">文档</a>
</footer>
</div>
);

View File

@ -20,11 +20,11 @@ const ProjectName = ({ name, setName, onSaveName, onSubmit, error, description,
{error && <span className="error">{error}</span>}
</div>
<div className="field field--wide">
<label htmlFor="project_description">Description</label>
<label htmlFor="project_description">描述</label>
<textarea
name="description"
id="project_description"
placeholder="Optional description of your project"
placeholder="描述你的项目(可选)"
rows="4"
value={description}
onChange={e => setDescription(e.target.value)}
@ -55,7 +55,7 @@ export const CreateProject = ({ onClose }) => {
const steps = {
name: <span className={tabClass.mod({ disabled: !!error })}>项目名称</span>,
import: <span className={tabClass.mod({ disabled: uploadDisabled })}>数据导入</span>,
config: "Labeling Setup",
config: "标注配置",
};
// name intentionally skipped from deps:
@ -117,12 +117,12 @@ export const CreateProject = ({ onClose }) => {
<Modal onHide={() => history.push("/projects")} fullscreen visible bare closeOnClickOutside={false}>
<div className={rootClass}>
<Modal.Header>
<h1>Create Project</h1>
<h1>创建项目</h1>
<ToggleItems items={steps} active={step} onSelect={setStep} />
<Space>
<Button look="danger" size="compact" onClick={onDelete} waiting={waiting}>Delete</Button>
<Button look="primary" size="compact" onClick={onCreate} waiting={waiting || uploading} disabled={!project || uploadDisabled || error}>Save</Button>
<Button look="danger" size="compact" onClick={onDelete} waiting={waiting}>删除</Button>
<Button look="primary" size="compact" onClick={onCreate} waiting={waiting || uploading} disabled={!project || uploadDisabled || error}>保存</Button>
</Space>
</Modal.Header>
<ProjectName

View File

@ -55,8 +55,7 @@ const Footer = () => {
return (
<Modal.Footer>
<IconInfo className={importClass.elem("info-icon")} width="20" height="20" />
See the&nbsp;documentation to <a target="_blank" href="https://labelstud.io/guide/predictions.html">import preannotated data</a>{" "}
or&nbsp;to <a target="_blank" href="https://labelstud.io/guide/storage.html">sync data from a&nbsp;database or&nbsp;cloud storage</a>.
查看文档了解如何<a target="_blank" href="https://labelstud.io/guide/predictions.html">导入预标注数据</a>或<a target="_blank" href="https://labelstud.io/guide/storage.html">从数据库或云存储中同步数据</a>
</Modal.Footer>
);
};
@ -173,7 +172,7 @@ export const ImportPage = ({
console.error(err);
// @todo workaround for error about input size in a wrong html format
if (typeof err === "string" && err.includes("RequestDataTooBig")) {
const message = "Imported file is too big";
const message = "导入的文件太大";
const extra = err.match(/"exception_value">(.*)<\/pre>/)?.[1];
err = { message, extra };
}
@ -271,22 +270,22 @@ export const ImportPage = ({
<header>
<form className={importClass.elem("url-form") + " inline"} method="POST" onSubmit={onLoadURL}>
<input placeholder="Dataset URL" name="url" ref={urlRef} />
<button type="submit">Add URL</button>
<input placeholder="数据集的 URL" name="url" ref={urlRef} />
<button type="submit">添加 URL</button>
</form>
<span>or</span>
<span></span>
<button onClick={() => document.getElementById('file-input').click()} className={importClass.elem("upload-button")}>
<IconUpload width="16" height="16" className={importClass.elem("upload-icon")} />
Upload {files.uploaded.length ? "More " : ""}Files
上传 {files.uploaded.length ? "更多 " : ""}文件
</button>
<div className={importClass.elem("csv-handling").mod({ highlighted: highlightCsvHandling, hidden: !csvHandling })}>
<span>Treat CSV/TSV as</span>
<label><input {...csvProps} value="tasks" checked={csvHandling === "tasks"}/> List of tasks</label>
<label><input {...csvProps} value="ts" checked={csvHandling === "ts"}/> Time Series</label>
<span>CSV/TSV 作为</span>
<label><input {...csvProps} value="tasks" checked={csvHandling === "tasks"}/> 任务列表</label>
<label><input {...csvProps} value="ts" checked={csvHandling === "ts"}/> 时间序列</label>
</div>
<div className={importClass.elem("status")}>
{files.uploaded.length
? `${files.uploaded.length} files uploaded`
? `${files.uploaded.length} 个文件已上传`
: ""}
</div>
</header>
@ -298,15 +297,15 @@ export const ImportPage = ({
{!showList && (
<label htmlFor="file-input">
<div className={dropzoneClass.elem("content")}>
<header>Drag & drop files here<br/>or click to browse</header>
<header>将文件拖放到此处<br/>或单击浏览</header>
<IconUpload height="64" className={dropzoneClass.elem("icon")} />
<dl>
<dt>Text</dt><dd>txt</dd>
<dt>Audio</dt><dd>wav, aiff, mp3, au, flac, m4a, ogg</dd>
<dt>Images</dt><dd>jpg, png, gif, bmp, svg, webp</dd>
<dt>文本</dt><dd>txt</dd>
<dt>音频</dt><dd>wav, aiff, mp3, au, flac, m4a, ogg</dd>
<dt>图像</dt><dd>jpg, png, gif, bmp, svg, webp</dd>
<dt>HTML</dt><dd>html, htm, xml</dd>
<dt>Time Series</dt><dd>csv, tsv</dd>
<dt>Common Formats</dt><dd>csv, tsv, txt, json</dd>
<dt>时间序列</dt><dd>csv, tsv</dd>
<dt>常见格式</dt><dd>csv, tsv, txt, json</dd>
</dl>
</div>
</label>

View File

@ -62,12 +62,12 @@ export const Inner = () => {
bare
>
<Modal.Header divided>
<Elem block="modal" name="title">Import Data</Elem>
<Elem block="modal" name="title">导入数据</Elem>
<Space>
<Button waiting={waiting} onClick={onCancel}>Cancel</Button>
<Button waiting={waiting} onClick={onCancel}>取消</Button>
<Button look="primary" onClick={onFinish} waiting={waiting || uploading} disabled={uploadDisabled}>
Import
导入
</Button>
</Space>
</Modal.Header>

View File

@ -11,12 +11,12 @@ export const useDraftProject = () => {
// always create the new one
const lastIndex = (projects ?? []).length;
let projectNumber = lastIndex + 1;
let projectName = `New Project #${projectNumber}`;
let projectName = `新项目 #${projectNumber}`;
// dirty hack to get proper non-duplicate name
while(projects.find(({title}) => title === projectName)) {
projectNumber++;
projectName = `New Project #${projectNumber}`;
projectName = `新项目 #${projectNumber}`;
}
const draft = await api.callApi('createProject', {

View File

@ -104,10 +104,10 @@ export const DataManagerPage = ({...props}) => {
return crashed ? (
<Block name="crash">
<Elem name="info">Project was deleted or not yet created</Elem>
<Elem name="info">项目已删除或尚未创建</Elem>
<Button to="/projects">
Back to projects
返回项目列表
</Button>
</Block>
) : (
@ -127,9 +127,9 @@ DataManagerPage.context = ({dmRef}) => {
const [mode, setMode] = useState(dmRef?.mode ?? "explorer");
const links = {
'/settings': 'Settings',
'/data/import': "Import",
'/data/export': 'Export',
'/settings': '设置',
'/data/import': "导入",
'/data/export': '导出',
};
const updateCrumbs = (currentMode) => {
@ -147,7 +147,7 @@ DataManagerPage.context = ({dmRef}) => {
});
addCrumb({
key: "dm-crumb",
title: "Labeling",
title: "标注",
});
}
};
@ -158,7 +158,7 @@ DataManagerPage.context = ({dmRef}) => {
if (isLabelStream && show_instruction && expert_instruction) {
modal({
title: "Labeling Instructions",
title: "标注指引",
body: <div dangerouslySetInnerHTML={{__html: expert_instruction}}/>,
style: { width: 680 },
});
@ -186,11 +186,11 @@ DataManagerPage.context = ({dmRef}) => {
{(project.expert_instruction && mode !== 'explorer') && (
<Button size="compact" onClick={() => {
modal({
title: "Instructions",
title: "指引",
body: () => <div dangerouslySetInnerHTML={{__html: project.expert_instruction}}/>,
});
}}>
Instructions
指引
</Button>
)}

View File

@ -155,11 +155,11 @@ export const ExportPage = () => {
const search = location.search;
history.replace(`${path}${search !== '?' ? search : ''}`);
}}
title="Export data"
title="导出数据"
style={{width: 720}}
closeOnClickOutside={false}
allowClose={!downloading}
// footer="Read more about supported export formats in the Documentation."
// footer="在文档中了解支持的导出格式"
visible
>
<Block name="export-page">
@ -221,7 +221,7 @@ export const ExportPage = () => {
const FormatInfo = ({availableFormats, selected, onClick}) => {
return (
<Block name="formats">
<Elem name="info">You can export dataset in one of the following formats:</Elem>
<Elem name="info">你可以将数据集导出为以下格式</Elem>
<Elem name="list">
{availableFormats.map(format => (
<Elem
@ -247,11 +247,11 @@ const FormatInfo = ({availableFormats, selected, onClick}) => {
</Elem>
))}
</Elem>
<Elem name="feedback">
Can't find an export format?
{/* <Elem name="feedback">
没有找到导出格式
<br/>
Please let us know in <a className="no-go" href="http://slack.labelstud.io.s3-website-us-east-1.amazonaws.com?source=product-export">Slack</a> or submit an issue to the <a className="no-go" href="https://github.com/heartexlabs/label-studio-converter/issues">Repository</a>
</Elem>
请在 <a className="no-go" href="http://slack.labelstud.io.s3-website-us-east-1.amazonaws.com?source=product-export">Slack</a> 中告诉我们,或者提交 <a className="no-go" href="https://github.com/heartexlabs/label-studio-converter/issues">仓库</a>
</Elem> */}
</Block>
);
};

View File

@ -211,7 +211,6 @@ export const ExportToPlatform = () => {
<Elem
tag={Button}
name="finish"
look="primary"
onClick={handleClose}
waiting={downloading}
>

View File

@ -43,9 +43,9 @@ export const PeopleList = ({onSelect, selectedUser, defaultSelected}) => {
<Elem name="users">
<Elem name="header">
<Elem name="column" mix="avatar"/>
<Elem name="column" mix="email">Email</Elem>
<Elem name="column" mix="name">Name</Elem>
<Elem name="column" mix="last-activity">Last Activity</Elem>
<Elem name="column" mix="email">邮箱</Elem>
<Elem name="column" mix="name">姓名</Elem>
<Elem name="column" mix="last-activity">最近活动</Elem>
</Elem>
<Elem name="body">
{usersList.map(({user}) => {

View File

@ -24,7 +24,7 @@ const InvitationModal = ({link}) => {
/>
<Description style={{width: '70%', marginTop: 16}}>
Invite people to join your Label Studio instance. People that you invite have full access to all of your projects. <a href="https://labelstud.io/guide/signup.html">Learn more</a>.
邀请您的同事加入Label Studio实例被邀请的人将拥有对您所有项目的完全访问权限<a href="https://labelstud.io/guide/signup.html">了解更多</a>
</Description>
</Block>
);
@ -74,7 +74,7 @@ export const PeoplePage = () => {
<Space spread>
<Space>
<Button style={{width: 170}} onClick={() => updateLink()}>
Reset Link
重置链接
</Button>
</Space>
<Space>
@ -114,7 +114,7 @@ export const PeoplePage = () => {
<Space>
<Button icon={<LsPlus/>} primary onClick={showInvitationModal}>
Add People
添加人员
</Button>
</Space>
</Space>

View File

@ -45,22 +45,22 @@ export const SelectedUser = ({ user, onClose }) => {
{!!user.created_projects.length && (
<Elem name="section">
<Elem name="section-title">Created Projects</Elem>
<Elem name="section-title">创建的项目</Elem>
<UserProjectsLinks projects={user.created_projects}/>
</Elem>
)}
{!!user.contributed_to_projects.length && (
<Elem name="section">
<Elem name="section-title">Contributed to</Elem>
<Elem name="section-title">参与项目</Elem>
<UserProjectsLinks projects={user.contributed_to_projects}/>
</Elem>
)}
<Elem tag="p" name="last-active">
Last activity on: {format(new Date(user.last_activity), 'dd MMM yyyy, KK:mm a')}
上次活动于{format(new Date(user.last_activity), 'yyyy-MM-dd HH:mm:ss')}
</Elem>
</Block>
);

View File

@ -59,7 +59,7 @@ export const ProjectsPage = () => {
);
};
ProjectsPage.title = "Projects";
ProjectsPage.title = "项目";
ProjectsPage.path = "/projects";
ProjectsPage.exact = true;
ProjectsPage.routes = ({store}) => [
@ -79,5 +79,5 @@ ProjectsPage.routes = ({store}) => [
];
ProjectsPage.context = ({ openModal, showButton }) => {
if (!showButton) return null;
return <Button onClick={openModal} look="primary" size="compact">Create</Button>;
return <Button onClick={openModal} look="primary" size="compact">创建</Button>;
};

View File

@ -48,7 +48,7 @@ const ProjectCard = ({project, history}) => {
<Elem name="header">
<Elem name="title">
<Elem name="title-text">
{project.title ?? "New project"}
{project.title ?? "新项目"}
</Elem>
<Elem name="menu" onClick={(e) => {
@ -57,8 +57,8 @@ const ProjectCard = ({project, history}) => {
}}>
<Dropdown.Trigger content={(
<Menu>
<Menu.Item href={`/projects/${project.id}/settings`}>Settings</Menu.Item>
<Menu.Item href={`/projects/${project.id}/data?labeling=1`}>Label</Menu.Item>
<Menu.Item href={`/projects/${project.id}/settings`}>设置</Menu.Item>
<Menu.Item href={`/projects/${project.id}/data?labeling=1`}>标注</Menu.Item>
</Menu>
)}>
<Button size="small" type="text" icon={<LsEllipsis/>}/>
@ -92,7 +92,7 @@ const ProjectCard = ({project, history}) => {
</Elem>
<Elem name="info">
<Elem name="created-date">
{format(new Date(project.created_at), "dd MMM yy, HH:mm")}
{format(new Date(project.created_at), "yyyy-MM-dd HH:mm")}
</Elem>
<Elem name="created-by">
<Userpic src="#" user={project.created_by} showUsername/>

View File

@ -16,9 +16,9 @@ export const DangerZone = () => {
const handleOnClick = (type) => () => {
confirm({
title: "Action confirmation",
body: "You're about to delete all things. This action cannot be undone.",
okText: "Proceed",
title: "操作确认",
body: "您正在执行一项不可逆的操作,这个操作不能撤销。",
okText: "继续",
buttonLook: "destructive",
onOk: async () => {
setProcessing(type);
@ -47,31 +47,33 @@ export const DangerZone = () => {
});
};
const buttons = useMemo(() => [{
type: 'annotations',
disabled: true, //&& !project.total_annotations_number,
label: `Delete ${project.total_annotations_number} Annotations`,
label: `删除 ${project.total_annotations_number} 个标注`,
}, {
type: 'tasks',
disabled: true, //&& !project.task_number,
label: `Delete ${project.task_number} Tasks`,
label: `删除 ${project.task_number} 个任务`,
}, {
type: 'predictions',
disabled: true, //&& !project.total_predictions_number,
label: `Delete ${project.total_predictions_number} Predictions`,
label: `删除 ${project.total_predictions_number} 个预测`,
}, {
type: 'tabs',
label: `Drop All Tabs`,
label: '删除所有标签页',
}, {
type: 'project',
label: 'Delete Project',
label: '删除项目',
}], [project]);
return (
<div style={{width: 480}}>
<Label
text="Delete Annotations, Tasks, or Project"
description="Perform these actions at your own risk. Actions you take on this page can't be reverted. Make sure your data is backed up."
text="删除标注、任务或项目"
description="这些操作会立即生效,并且无法回滚。请确保您的数据已经备份,并且在继续之前认真考虑每一个选项的含义。"
style={{display: 'block', width: 415}}
/>
@ -96,5 +98,5 @@ export const DangerZone = () => {
);
};
DangerZone.title = "Danger Zone";
DangerZone.title = "危险区域";
DangerZone.path = "/danger-zone";

View File

@ -24,8 +24,8 @@ export const GeneralSettings = () => {
];
const samplings = [
{value: "Sequential", label: "Sequential", description: "Tasks are ordered by Data manager ordering"},
{value: "Uniform", label: "Random", description: "Tasks are chosen with uniform random"},
{value: "Sequential", label: "顺序", description: "按数据管理器的顺序来排列任务"},
{value: "Uniform", label: "随机", description: "以均匀随机方式选择任务"},
];
return (
@ -39,18 +39,18 @@ export const GeneralSettings = () => {
<Form.Row columnCount={1} rowGap="32px">
<Input
name="title"
label="Project Name"
label="项目名称"
labelProps={{large: true}}
/>
<TextArea
name="description"
label="Description"
label="项目描述"
labelProps={{large: true}}
style={{minHeight: 128}}
/>
<RadioGroup name="color" label="Color" size="large" labelProps={{size: "large"}}>
<RadioGroup name="color" label="颜色" size="large" labelProps={{size: "large"}}>
{colors.map(color => (
<RadioGroup.Button key={color} value={color}>
<Block name="color" style={{'--background': color}}/>
@ -58,12 +58,12 @@ export const GeneralSettings = () => {
))}
</RadioGroup>
<RadioGroup label="Task Sampling" labelProps={{size: "large"}} name="sampling" simple>
<RadioGroup label="任务采样" labelProps={{size: "large"}} name="sampling" simple>
{samplings.map(({value, label, description}) => (
<RadioGroup.Button
key={value}
value={`${value} sampling`}
label={`${label} sampling`}
label={`${label}采样`}
description={description}
/>
))}
@ -72,15 +72,15 @@ export const GeneralSettings = () => {
<Form.Actions>
<Form.Indicator>
<span case="success">Saved!</span>
<span case="success">保存!</span>
</Form.Indicator>
<Button type="submit" look="primary" style={{width: 120}}>Save</Button>
<Button type="submit" look="primary" style={{width: 120}}>保存</Button>
</Form.Actions>
</Form>
</div>
);
};
GeneralSettings.menuItem = "General";
GeneralSettings.menuItem = "常用设置";
GeneralSettings.path = "/";
GeneralSettings.exact = true;

View File

@ -21,12 +21,12 @@ export const InstructionsSettings = () => {
<div style={{width: 480}}>
<Form ref={formRef} action="updateProject" formData={{...project}} params={{pk: project.id}} onSubmit={updateProject}>
<Form.Row columnCount={1}>
<Label text="Labeling Instructions" large/>
<Label text="标注说明" large/>
<div style={{paddingLeft: 16}}>
<Toggle label="Show before labeling" name="show_instruction"/>
<Toggle label="标注前显示" name="show_instruction"/>
</div>
<div style={{color: "rgba(0,0,0,0.4)", paddingLeft: 16}}>
Write instructions to help users complete labeling tasks.
编写标注说明以帮助用户完成标注任务
</div>
</Form.Row>
@ -36,14 +36,14 @@ export const InstructionsSettings = () => {
<Form.Actions>
<Form.Indicator>
<span case="success">Saved!</span>
<span case="success">保存成功</span>
</Form.Indicator>
<Button type="submit" look="primary" style={{width: 120}}>Save</Button>
<Button type="submit" look="primary" style={{width: 120}}>保存</Button>
</Form.Actions>
</Form>
</div>
);
};
InstructionsSettings.title = "Instructions";
InstructionsSettings.title = "标注说明";
InstructionsSettings.path = "/instruction";

View File

@ -48,12 +48,13 @@ export const LabelingSettings = () => {
const onSave = useCallback(async () => {
if (essentialDataChanged && projectAlreadySetUp) {
confirm({
title: "Config data changed",
body: "Labeling config has essential changes that affect data displaying. Saving the config may lead to deleting all tabs previously created in the Data Manager.",
title: "标注配置数据已更改",
body: "标注配置数据有影响数据展示的重大更改。保存配置可能会导致删除数据管理器中以前创建的所有标签页。",
buttonLook: "destructive",
onOk: () => saveConfig(),
okText: "Save",
okText: "保存",
});
} else {
saveConfig();
}
@ -81,5 +82,5 @@ export const LabelingSettings = () => {
);
};
LabelingSettings.title = "Labeling Interface";
LabelingSettings.title = "标识接口";
LabelingSettings.path = "/labeling";

View File

@ -49,8 +49,8 @@ export const MachineLearningList = ({ backends, fetchBackends, onEdit }) => {
const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
const confirmDelete = useCallback((backend) => {
confirm({
title: "Delete ML Backend",
body: "This action cannot be undone. Are you sure?",
title: "删除机器学习后端",
body: "此操作不可撤销。您确定要这样做吗?",
buttonLook: "destructive",
onOk(){ onDelete?.(backend); },
});
@ -63,8 +63,8 @@ const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
<Dropdown.Trigger align="right" content={(
<Menu size="small">
<Menu.Item onClick={() => onEdit(backend)}>Edit</Menu.Item>
<Menu.Item onClick={() => confirmDelete(backend)}>Delete</Menu.Item>
<Menu.Item onClick={() => onEdit(backend)}>编辑</Menu.Item>
<Menu.Item onClick={() => confirmDelete(backend)}>删除</Menu.Item>
</Menu>
)}>
<Button type="link" icon={<FaEllipsisV/>}/>
@ -77,17 +77,17 @@ const BackendCard = ({backend, onStartTrain, onEdit, onDelete}) => {
</DescriptionList.Item>
{backend.description && (
<DescriptionList.Item
term="Description"
term="描述"
children={backend.description}
/>
)}
<DescriptionList.Item term="Version">
{backend.version ? format(new Date(backend.version), 'MMMM dd, yyyy ∙ HH:mm:ss') : 'unknown'}
<DescriptionList.Item term="版本">
{backend.version ? format(new Date(backend.version), 'yyyy-MM-dd HH:mm:ss') : '未知'}
</DescriptionList.Item>
</DescriptionList>
<Button disabled={backend.state !== "CO"} onClick={() => onStartTrain(backend)}>
Start Training
开始训练
</Button>
</Card>
);
@ -99,12 +99,13 @@ const BackendState = ({backend}) => {
<div className={cn('ml').elem('status')}>
<span className={cn('ml').elem('indicator').mod({state})}></span>
<Oneof value={state} className={cn('ml').elem('status-label')}>
<span case="DI">Disconnected</span>
<span case="CO">Connected</span>
<span case="ER">Error</span>
<span case="TR">Training</span>
<span case="PR">Predicting</span>
<span case="DI">断开连接</span>
<span case="CO">已连接</span>
<span case="ER">错误</span>
<span case="TR">训练中</span>
<span case="PR">预测中</span>
</Oneof>
</div>
);
};

View File

@ -51,7 +51,7 @@ export const MachineLearningSettings = () => {
const showMLFormModal = useCallback((backend) => {
const action = backend ? "updateMLBackend" : "addMLBackend";
const modalProps = {
title: `${backend ? 'Edit' : 'Add'} model`,
title: `${backend ? '编辑' : '添加'}模型`,
style: { width: 760 },
closeOnClickOutside: false,
body: (
@ -69,17 +69,17 @@ export const MachineLearningSettings = () => {
<Input type="hidden" name="project" value={project.id}/>
<Form.Row columnCount={2}>
<Input name="title" label="Title" placeholder="ML Model"/>
<Input name="title" label="标题" placeholder="ML 模型"/>
<Input name="url" label="URL" required/>
</Form.Row>
<Form.Row columnCount={1}>
<TextArea name="description" label="Description" style={{minHeight: 120}}/>
<TextArea name="description" label="描述" style={{minHeight: 120}}/>
</Form.Row>
<Form.Actions>
<Button type="submit" look="primary" onClick={() => setMLError(null)}>
Validate and Save
验证并保存
</Button>
</Form.Actions>
@ -88,7 +88,7 @@ export const MachineLearningSettings = () => {
{response.error_message && (
<ErrorWrapper error={{
response: {
detail: `Failed to ${backend ? 'save' : 'add new'} ML backend.`,
detail: `保存或添加新的 ML 后端操作失败。`,
exc_info: response.error_message,
},
}}/>
@ -114,15 +114,16 @@ export const MachineLearningSettings = () => {
return (
<>
<Description style={{marginTop: 0, maxWidth: 680}}>
Add one or more machine learning models to predict labels for your data.
To import predictions without connecting a model,
添加一个或多个机器学习模型以便为您的数据预测标签
要在不连接模型的情况下导入预测
{" "}
<a href="https://labelstud.io/guide/predictions.html" target="_blank">
see the documentation
</a>.
请参阅文档
</a>
</Description>
<Button onClick={() => showMLFormModal()}>
Add Model
添加模型
</Button>
<Divider height={32}/>
@ -134,25 +135,25 @@ export const MachineLearningSettings = () => {
autosubmit
>
<Form.Row columnCount={1}>
<Label text="ML-Assisted Labeling" large/>
<Label text="机器学习辅助标注" large/>
<div style={{paddingLeft: 16}}>
<Toggle
label="Start model training after any annotations are submitted or updated"
label="在任何标注或更新时启动模型训练"
name="start_training_on_annotation_update"
/>
</div>
<div style={{paddingLeft: 16}}>
<Toggle
label="Retrieve predictions when loading a task automatically"
label="在任务加载时自动检索预测结果"
name="evaluate_predictions_automatically"
/>
</div>
<div style={{paddingLeft: 16}}>
<Toggle
label="Show predictions to annotators in the Label Stream and Quick View"
label="在标签流和快速查看中显示预测结果给标注者"
name="show_collab_predictions"
/>
</div>
@ -161,8 +162,8 @@ export const MachineLearningSettings = () => {
{versions.length > 1 && (
<Form.Row columnCount={1}>
<Label
text="Model Version"
description="Model version allows you to specify which prediction will be shown to the annotators."
text="模型版本"
description="模型版本允许您指定要显示给标注者的预测"
style={{marginTop: 16}}
large
/>
@ -175,12 +176,12 @@ export const MachineLearningSettings = () => {
options={[
...versions,
]}
placeholder="No model version selected"
placeholder="未选择模型版本"
/>
</div>
<Button onClick={resetMLVersion}>
Reset
重置
</Button>
</div>
@ -195,7 +196,8 @@ export const MachineLearningSettings = () => {
/>
</>
);
};
MachineLearningSettings.title = "Machine Learning";
MachineLearningSettings.title = "机器学习";
MachineLearningSettings.path = "/ml";

View File

@ -44,12 +44,12 @@ export const StorageCard = ({
return (
<Card
header={storageData.title ?? `Untitled ${storageData.type}`}
header={storageData.title ?? `无标题 ${storageData.type}`}
extra={(
<Dropdown.Trigger align="right" content={(
<Menu size="compact" style={{width: 110}}>
<Menu.Item onClick={() => onEditStorage(storageData)}>Edit</Menu.Item>
<Menu.Item onClick={() => onDeleteStorage(storageData)}>Delete</Menu.Item>
<Menu.Item onClick={() => onEditStorage(storageData)}>编辑</Menu.Item>
<Menu.Item onClick={() => onDeleteStorage(storageData)}>删除</Menu.Item>
</Menu>
)}>
<Button type="link" style={{width: 32, height: 32, marginRight: -10}} icon={<FaEllipsisV/>}/>
@ -67,12 +67,12 @@ export const StorageCard = ({
<div className={rootClass.elem('sync')}>
<Space size="small">
<Button waiting={syncing} onClick={startSync}>
Sync Storage
同步存储
</Button>
{synced !== null ? (
<div className={rootClass.elem('sync-count')}>
Synced {synced} task(s)
已同步 {synced} 个任务
</div>
) : null}
</Space>

View File

@ -35,7 +35,7 @@ export const StorageForm = forwardRef(({
skip: true,
type: "select",
name: "storage_type",
label: "Storage Type",
label: "存储类型",
disabled: !!storage,
options: storageTypes.map(({name, title}) => ({
value: name, label: title,
@ -92,20 +92,21 @@ export const StorageForm = forwardRef(({
<Form.Actions valid={connectionValid} extra={(connectionValid !== null) && (
<Block name="form-indicator">
<Oneof value={connectionValid}>
<Elem tag="span" mod={{type: "success"}} name="item" case={true}>Successfully connected!</Elem>
<Elem tag="span" mod={{type: "fail"}} name="item" case={false}>Connection failed</Elem>
<Elem tag="span" mod={{type: "success"}} name="item" case={true}>连接成功</Elem>
<Elem tag="span" mod={{type: "fail"}} name="item" case={false}>连接失败</Elem>
</Oneof>
</Block>
)}>
<Input type="hidden" name="project" value={project}/>
<Button.Group className={rootClass.elem('buttons')}>
<Button type="button" waiting={checking} onClick={validateStorageConnection}>
Check Connection
检查连接
</Button>
<Button type="submit" look="primary">{storage ? "Save" : "Add Storage"}</Button>
<Button type="submit" look="primary">{storage ? "保存" : "添加存储"}</Button>
</Button.Group>
</Form.Actions>
<InlineError/>
</Form.Builder>
);

View File

@ -56,9 +56,9 @@ export const StorageSet = ({title, target, rootClass, buttonLabel}) => {
}, [project]);
const showStorageFormModal = useCallback((storage) => {
const action = storage ? "Edit" : "Add";
const actionTarget = target === 'export' ? 'Target' : 'Source';
const title = `${action} ${actionTarget} Storage`;
const action = storage ? "编辑" : "添加";
const actionTarget = target === 'export' ? '目标' : '源';
const title = `${action}${actionTarget}存储`;
const modalRef = modal({
title,
@ -79,9 +79,9 @@ export const StorageSet = ({title, target, rootClass, buttonLabel}) => {
),
footer: (
<>
Save completed annotations to Amazon S3, Google Cloud, Microsoft Azure, or Redis.
将已完成的标注数据保存到 Amazon S3Google CloudMicrosoft Azure Redis
<br/>
<a href="https://labelstud.io/guide/storage.html">See more in the documentation</a>.
了解更多请参考<a href="https://labelstud.io/guide/storage.html">文档</a>
</>
),
});
@ -93,8 +93,8 @@ export const StorageSet = ({title, target, rootClass, buttonLabel}) => {
const onDeleteStorage = useCallback(async (storage) => {
confirm({
title: "Deleting storage",
body: "This action cannot be undone. Are you sure?",
title: "删除存储",
body: "这个操作是不可恢复的,您确定要删除吗?",
buttonLook: "destructive",
onOk: async () => {
const response = await api.callApi('deleteStorage', {
@ -110,6 +110,7 @@ export const StorageSet = ({title, target, rootClass, buttonLabel}) => {
});
}, [fetchStorages]);
useEffect(() => {
fetchStorages();
}, [fetchStorages]);

View File

@ -11,24 +11,25 @@ export const StorageSettings = () => {
return (
<Block name="storage-settings">
<Description style={{marginTop: 0}}>
Use cloud or database storage as the source for your labeling tasks or the target of your completed annotations.
使用云存储或数据库为您的标注任务或已完成的标注作为数据源或者作为数据的目标
</Description>
<Columns count={2} gap="40px" size="320px" className={rootClass}>
<StorageSet
title="Source Cloud Storage"
title="源云存储"
buttonLabel="添加源存储"
rootClass={rootClass}
/>
<StorageSet
title="Target Cloud Storage"
title="目标云存储"
target="export"
buttonLabel="添加目标存储"
rootClass={rootClass}
/>
</Columns>
</Block>
);
};

View File

@ -7,7 +7,7 @@ export const StorageSummary = ({storage, className, enableLastSync = false, stor
return (
<div className={className}>
<DescriptionList>
<DescriptionList.Item term="Type">
<DescriptionList.Item term="类型">
{storageTypes.find(s => s.name === storage.type)?.title ?? storage.type}
</DescriptionList.Item>
<Oneof value={storage.type}>
@ -18,8 +18,8 @@ export const StorageSummary = ({storage, className, enableLastSync = false, stor
<LocalStorage case="localfiles" storage={storage}/>
</Oneof>
{enableLastSync && (
<DescriptionList.Item term="Last Sync">
{storage.last_sync ? format(new Date(storage.last_sync), 'MMMM dd, yyyy ∙ HH:mm:ss') : "Never synced"}
<DescriptionList.Item term="最后一次同步">
{storage.last_sync ? format(new Date(storage.last_sync), 'yyyy-MM-dd HH:mm:ss') : "不同步"}
</DescriptionList.Item>
)}
</DescriptionList>
@ -45,7 +45,7 @@ const GSCStorage = ({storage}) => {
const AzureStorage = ({storage}) => {
return (
<DescriptionList.Item term="Container">
<DescriptionList.Item term="容器">
{storage.container}
</DescriptionList.Item>
);
@ -54,10 +54,10 @@ const AzureStorage = ({storage}) => {
const RedisStorage = ({storage}) => {
return (
<>
<DescriptionList.Item term="Path">
<DescriptionList.Item term="路径">
{storage.path}
</DescriptionList.Item>
<DescriptionList.Item term="Host">
<DescriptionList.Item term="主机">
{storage.host}{storage.port ? `:${storage.post}` : ''}
</DescriptionList.Item>
</>
@ -66,8 +66,9 @@ const RedisStorage = ({storage}) => {
const LocalStorage = ({storage}) => {
return (
<DescriptionList.Item term="Path">
<DescriptionList.Item term="路径">
{storage.path}
</DescriptionList.Item>
);
};

View File

@ -16,7 +16,7 @@ const errorFormatter = (result) => {
return {
isShutdown,
title: result.error ? "Runtime error" : "Server error",
title: result.error ? "运行时错误" : "服务员错误",
message: response?.detail ?? result?.error,
stacktrace: response?.exc_info ?? null,
version: response?.version,
@ -45,8 +45,8 @@ const handleError = async (response, showModal = true) => {
body: isShutdown ? (
<ErrorWrapper
possum={false}
title={"Connection refused"}
message={"Server not responding. Is it still running?"}
title={"无法连接"}
message={"服务器无响应"}
/>
) : (
<ErrorWrapper {...formattedError}/>