chore: update locales

This commit is contained in:
jialin
2025-10-13 15:21:17 +08:00
parent bb7c8e9c85
commit f42f159785
15 changed files with 196 additions and 33 deletions
+4 -1
View File
@@ -12,5 +12,8 @@ export default {
'apikeys.form.expiration.1month': '1 month', 'apikeys.form.expiration.1month': '1 month',
'apikeys.form.expiration.6months': '6 months', 'apikeys.form.expiration.6months': '6 months',
'apikeys.form.expiration.never': 'No expiration', 'apikeys.form.expiration.never': 'No expiration',
'apikeys.table.bindModels': 'Allowed Models' 'apikeys.table.bindModels': 'Model Access',
'apikeys.models.all': 'All models',
'apikeys.models.selected': 'Selected models',
'apikeys.models.noModelsFound': 'No models found'
}; };
+12 -1
View File
@@ -170,5 +170,16 @@ export default {
'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding', 'models.form.enableSpeculativeDecoding': 'Enable Speculative Decoding',
'models.form.check.clusterUnavailable': 'Current cluster is unavailable', 'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
'models.form.check.otherClustersAvailable': 'models.form.check.otherClustersAvailable':
'Available clusters: {clusters}. Please switch cluster.' 'Available clusters: {clusters}. Please switch cluster.',
'models.button.accessSettings': 'Access Settings',
'models.table.accessScope': 'Access Scope',
'models.table.accessScope.all': 'All users',
'models.table.accessScope.selected': 'Selected users',
'models.table.userSelection': 'User Selection',
'models.table.filterByName': 'Filter by username',
'models.table.admin': 'Admin',
'models.table.noselected': 'No users selected',
'models.table.users.all': 'All Users',
'models.table.users.selected': 'Selected Users',
'models.table.nouserFound': 'No users found'
}; };
+11 -1
View File
@@ -12,5 +12,15 @@ export default {
'apikeys.form.expiration.1month': '1ヶ月', 'apikeys.form.expiration.1month': '1ヶ月',
'apikeys.form.expiration.6months': '6ヶ月', 'apikeys.form.expiration.6months': '6ヶ月',
'apikeys.form.expiration.never': '無期限', 'apikeys.form.expiration.never': '無期限',
'apikeys.table.bindModels': '許可されたモデル' 'apikeys.table.bindModels': 'Model Access',
'apikeys.models.all': 'All models',
'apikeys.models.selected': 'Selected models',
'apikeys.models.noModelsFound': 'No models found'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'apikeys.table.bindModels': 'Model Access',
// 2. 'apikeys.models.all': 'All models',
// 3. 'apikeys.models.selected': 'Selected models',
// 4. 'apikeys.models.noModelsFound': 'No models found'
// ========== End of To-Do List ==========
+27 -2
View File
@@ -167,7 +167,18 @@ export default {
'models.form.enableSpeculativeDecoding': '推測デコーディングを有効にする', 'models.form.enableSpeculativeDecoding': '推測デコーディングを有効にする',
'models.form.check.clusterUnavailable': 'Current cluster is unavailable', 'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
'models.form.check.otherClustersAvailable': 'models.form.check.otherClustersAvailable':
'Available clusters: {clusters}. Please switch cluster.' 'Available clusters: {clusters}. Please switch cluster.',
'models.button.accessSettings': 'Access Settings',
'models.table.accessScope': 'Access Scope',
'models.table.accessScope.all': 'All users',
'models.table.accessScope.selected': 'Selected users',
'models.table.userSelection': 'User Selection',
'models.table.filterByName': 'Filter by username',
'models.table.admin': 'Admin',
'models.table.noselected': 'No users selected',
'models.table.users.all': 'All Users',
'models.table.users.selected': 'Selected Users',
'models.table.nouserFound': 'No users found'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -175,5 +186,19 @@ export default {
// 2. 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.` // 2. 'models.ollama.deprecated.notice': `The Ollama model source has been deprecated as of v0.6.1. For more information, see the <a href="https://github.com/gpustack/gpustack/issues/1979" target="_blank">related GitHub issue</a>.`
// 3. 'models.backend.mindie.310p':'Ascend 310P only supports FP16, so you need to set --dtype=float16.', // 3. 'models.backend.mindie.310p':'Ascend 310P only supports FP16, so you need to set --dtype=float16.',
// 4. 'models.form.check.clusterUnavailable': 'Current cluster is unavailable', // 4. 'models.form.check.clusterUnavailable': 'Current cluster is unavailable',
// 5. 'models.form.check.otherClustersAvailable': 'Available clusters: {clusters}. Please switch cluster.' // 5. 'models.form.check.otherClustersAvailable': 'Available clusters: {clusters}. Please switch cluster.',
// 6. 'models.button.accessSettings': 'Access Settings',
// 7. 'models.table.accessScope': 'Access Scope',
// 8. 'models.table.accessScope.all': 'All users',
// 9. 'models.table.accessScope.selected': 'Selected users',
// 10. 'models.table.userSelection': 'User Selection',
// 11. 'models.table.filterByName': 'Filter by username',
// 12. 'models.table.admin': 'Admin',
// 13. 'models.table.noselected': 'No users selected'
// 14. 'models.table.uses.all': 'All users',
// 15. 'models.table.uses.selected': 'Selected users',
// 16. 'models.table.nouserFound': 'No users found',
// 17. 'models.table.users.all': 'All Users',
// 18. 'models.table.users.selected': 'Selected Users',
// 19. 'models.table.nouserFound': 'No users found'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+11 -1
View File
@@ -12,5 +12,15 @@ export default {
'apikeys.form.expiration.1month': '1 месяц', 'apikeys.form.expiration.1month': '1 месяц',
'apikeys.form.expiration.6months': '6 месяцев', 'apikeys.form.expiration.6months': '6 месяцев',
'apikeys.form.expiration.never': 'Без срока действия', 'apikeys.form.expiration.never': 'Без срока действия',
'apikeys.table.bindModels': 'Разрешенные модели' 'apikeys.table.bindModels': 'Доступ к моделям',
'apikeys.models.all': 'All models',
'apikeys.models.selected': 'Selected models',
'apikeys.models.noModelsFound': 'No models found'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'apikeys.table.bindModels': 'Model Access',
// 2. 'apikeys.models.all': 'All models',
// 3. 'apikeys.models.selected': 'Selected models',
// 4. 'apikeys.models.noModelsFound': 'No models found'
// ========== End of To-Do List ==========
+25 -3
View File
@@ -166,12 +166,34 @@ export default {
'models.form.gpuCount': 'GPU на реплику', 'models.form.gpuCount': 'GPU на реплику',
'models.form.gpuType': 'Тип GPU', 'models.form.gpuType': 'Тип GPU',
'models.form.optimizeLongPrompt': 'Оптимизировать длинные промпты', 'models.form.optimizeLongPrompt': 'Оптимизировать длинные промпты',
'models.form.enableSpeculativeDecoding': 'Включить спекулятивное декодирование', 'models.form.enableSpeculativeDecoding':
'Включить спекулятивное декодирование',
'models.form.check.clusterUnavailable': 'Текущий кластер недоступен', 'models.form.check.clusterUnavailable': 'Текущий кластер недоступен',
'models.form.check.otherClustersAvailable': 'models.form.check.otherClustersAvailable':
'Доступные кластеры: {clusters}. Пожалуйста, переключитесь на другой кластер.' 'Доступные кластеры: {clusters}. Пожалуйста, переключитесь на другой кластер.',
'models.button.accessSettings': 'Access Settings',
'models.table.accessScope': 'Access Scope',
'models.table.accessScope.all': 'All users',
'models.table.accessScope.selected': 'Selected users',
'models.table.userSelection': 'User Selection',
'models.table.filterByName': 'Filter by username',
'models.table.admin': 'Admin',
'models.table.noselected': 'No users selected',
'models.table.users.all': 'All users',
'models.table.users.selected': 'Selected users',
'models.table.nouserFound': 'No users found'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'models.button.accessSettings': 'Access Settings',
// 2. 'models.table.accessScope': 'Access Scope',
// 3. 'models.table.accessScope.all': 'All users',
// 4. 'models.table.accessScope.selected': 'Selected users',
// 5. 'models.table.userSelection': 'User Selection',
// 6. 'models.table.filterByName': 'Filter by username',
// 7. 'models.table.admin': 'Admin',
// 8. 'models.table.noselected': 'No users selected',
// 9. 'models.table.users.all': 'All users',
// 10. 'models.table.users.selected': 'Selected users',
// 11. 'models.table.nouserFound': 'No users found'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+4 -1
View File
@@ -11,5 +11,8 @@ export default {
'apikeys.form.expiration.1month': '1个月', 'apikeys.form.expiration.1month': '1个月',
'apikeys.form.expiration.6months': '6个月', 'apikeys.form.expiration.6months': '6个月',
'apikeys.form.expiration.never': '永不过期', 'apikeys.form.expiration.never': '永不过期',
'apikeys.table.bindModels': '绑定模型' 'apikeys.table.bindModels': '模型访问',
'apikeys.models.all': '所有模型',
'apikeys.models.selected': '指定模型',
'apikeys.models.noModelsFound': '未找到模型'
}; };
+12 -1
View File
@@ -160,5 +160,16 @@ export default {
'models.form.enableSpeculativeDecoding': '启用推测解码', 'models.form.enableSpeculativeDecoding': '启用推测解码',
'models.form.check.clusterUnavailable': '当前集群不可用', 'models.form.check.clusterUnavailable': '当前集群不可用',
'models.form.check.otherClustersAvailable': 'models.form.check.otherClustersAvailable':
'可用的集群有: {clusters}。请切换集群。' '可用的集群有: {clusters}。请切换集群。',
'models.button.accessSettings': '访问设置',
'models.table.accessScope': '访问范围',
'models.table.accessScope.all': '所有用户',
'models.table.accessScope.selected': '指定用户',
'models.table.userSelection': '用户选择',
'models.table.filterByName': '按用户名筛选',
'models.table.admin': '管理员',
'models.table.noselected': '未选择用户',
'models.table.users.all': '所有用户',
'models.table.users.selected': '已选用户',
'models.table.nouserFound': '未找到用户'
}; };
+12 -2
View File
@@ -1,4 +1,5 @@
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Checkbox, Empty, Input } from 'antd'; import { Button, Checkbox, Empty, Input } from 'antd';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -34,6 +35,7 @@ interface SelectPanelProps {
leftWidth?: number; leftWidth?: number;
options: Array<{ key: string; title: string }>; options: Array<{ key: string; title: string }>;
selectedKeys: string[]; selectedKeys: string[];
notFoundContent?: React.ReactNode;
onSelectChange: (selectedKeys: string[]) => void; onSelectChange: (selectedKeys: string[]) => void;
} }
@@ -43,8 +45,10 @@ const SelectPanel: React.FC<SelectPanelProps> = ({
options, options,
selectedKeys, selectedKeys,
searchPlaceholder, searchPlaceholder,
notFoundContent,
onSelectChange onSelectChange
}) => { }) => {
const intl = useIntl();
const [indeterminate, setIndeterminate] = React.useState(false); const [indeterminate, setIndeterminate] = React.useState(false);
const [checkAll, setCheckAll] = React.useState(false); const [checkAll, setCheckAll] = React.useState(false);
const [searchText, setSearchText] = useState(''); const [searchText, setSearchText] = useState('');
@@ -162,7 +166,12 @@ const SelectPanel: React.FC<SelectPanelProps> = ({
indeterminate={indeterminate} indeterminate={indeterminate}
onChange={handleCheckAllChange} onChange={handleCheckAllChange}
></Checkbox> ></Checkbox>
<span> {selectedKeys.length} selected</span> <span>
{intl.formatMessage(
{ id: 'common.select.count' },
{ count: selectedKeys.length }
)}
</span>
</span> </span>
<Input <Input
prefix={ prefix={
@@ -172,6 +181,7 @@ const SelectPanel: React.FC<SelectPanelProps> = ({
} }
size="small" size="small"
allowClear allowClear
status={'null' as any}
placeholder={searchPlaceholder} placeholder={searchPlaceholder}
style={{ style={{
width: 300, width: 300,
@@ -192,7 +202,7 @@ const SelectPanel: React.FC<SelectPanelProps> = ({
) : ( ) : (
<Empty <Empty
image={Empty.PRESENTED_IMAGE_SIMPLE} image={Empty.PRESENTED_IMAGE_SIMPLE}
description="No models found" description={notFoundContent}
></Empty> ></Empty>
)} )}
</Left> </Left>
+7 -1
View File
@@ -1,4 +1,5 @@
import { MoreOutlined } from '@ant-design/icons'; import { MoreOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Transfer, TransferProps } from 'antd'; import { Transfer, TransferProps } from 'antd';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -72,6 +73,8 @@ interface TransferInnerProps extends TransferProps {
} }
const TransferInner: React.FC<TransferInnerProps> = (props) => { const TransferInner: React.FC<TransferInnerProps> = (props) => {
const intl = useIntl();
const renderAllLabels = (info: { const renderAllLabels = (info: {
selectedCount: number; selectedCount: number;
totalCount: number; totalCount: number;
@@ -79,7 +82,10 @@ const TransferInner: React.FC<TransferInnerProps> = (props) => {
if (info.selectedCount) { if (info.selectedCount) {
return ( return (
<span style={{ color: 'var(--ant-color-text-secondary)' }}> <span style={{ color: 'var(--ant-color-text-secondary)' }}>
{info.selectedCount} selected {intl.formatMessage(
{ id: 'common.select.count' },
{ count: info.selectedCount }
)}
</span> </span>
); );
} }
@@ -1,7 +1,9 @@
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import useAppUtils from '@/hooks/use-app-utils';
import SelectPanel from '@/pages/_components/select-panel'; import SelectPanel from '@/pages/_components/select-panel';
import { queryModelsList } from '@/pages/llmodels/apis'; import { queryModelsList } from '@/pages/llmodels/apis';
import { useIntl } from '@umijs/max';
import { Divider, Form, Radio } from 'antd'; import { Divider, Form, Radio } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -18,6 +20,8 @@ const AllowModelsForm: React.FC<{
currentData?: Partial<ListItem> | null; currentData?: Partial<ListItem> | null;
action: PageActionType; action: PageActionType;
}> = ({ currentData, action }) => { }> = ({ currentData, action }) => {
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const allowedModelNames = Form.useWatch( const allowedModelNames = Form.useWatch(
'allowed_model_names', 'allowed_model_names',
@@ -57,7 +61,7 @@ const AllowModelsForm: React.FC<{
return ( return (
<div> <div>
<Divider></Divider> <Divider></Divider>
<Label>Model Access</Label> <Label>{intl.formatMessage({ id: 'apikeys.table.bindModels' })}</Label>
<Form.Item <Form.Item
name="allowed_type" name="allowed_type"
initialValue="all" initialValue="all"
@@ -65,17 +69,38 @@ const AllowModelsForm: React.FC<{
> >
<Radio.Group <Radio.Group
options={[ options={[
{ label: 'All models', value: 'all' }, {
{ label: 'Selected models', value: 'custom' } label: intl.formatMessage({ id: 'apikeys.models.all' }),
value: 'all'
},
{
label: intl.formatMessage({ id: 'apikeys.models.selected' }),
value: 'custom'
}
]} ]}
></Radio.Group> ></Radio.Group>
</Form.Item> </Form.Item>
<Form.Item name="allowed_model_names" hidden={allowedType === 'all'}> <Form.Item
name="allowed_model_names"
hidden={allowedType === 'all'}
rules={[
{
required: allowedType === 'custom',
message: getRuleMessage(
'select',
intl.formatMessage({ id: 'models.table.models' })
)
}
]}
>
<SelectPanel <SelectPanel
height={300} height={300}
searchPlaceholder="Search by model name" searchPlaceholder={intl.formatMessage({ id: 'common.filter.name' })}
options={modelList} options={modelList}
selectedKeys={allowedModelNames || []} selectedKeys={allowedModelNames || []}
notFoundContent={intl.formatMessage({
id: 'apikeys.models.noModelsFound'
})}
onSelectChange={(selectedKeys) => { onSelectChange={(selectedKeys) => {
form.setFieldsValue({ allowed_model_names: selectedKeys }); form.setFieldsValue({ allowed_model_names: selectedKeys });
}} }}
@@ -68,7 +68,11 @@ const useModelsColumns = ({
showTitle: false showTitle: false
}, },
render: (text: string[], record: ListItem) => ( render: (text: string[], record: ListItem) => (
<AutoTooltip ghost>{text?.join(', ')}</AutoTooltip> <AutoTooltip ghost>
{text?.length
? text?.join(', ')
: intl.formatMessage({ id: 'common.select.option' })}
</AutoTooltip>
) )
}, },
{ {
@@ -1,5 +1,6 @@
import TransferInner from '@/pages/_components/transfer'; import TransferInner from '@/pages/_components/transfer';
import { queryUsersList } from '@/pages/users/apis'; import { queryUsersList } from '@/pages/users/apis';
import { useIntl } from '@umijs/max';
import { Empty, Form, Radio } from 'antd'; import { Empty, Form, Radio } from 'antd';
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -22,6 +23,7 @@ interface AccessControlFormProps {
const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => { const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
const { currentData, onFinish } = props; const { currentData, onFinish } = props;
const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
const setPublic = Form.useWatch('set_public', form); const setPublic = Form.useWatch('set_public', form);
const [targetKeys, setTargetKeys] = useState<TransferKey[]>([]); const [targetKeys, setTargetKeys] = useState<TransferKey[]>([]);
@@ -42,7 +44,6 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
key: item.id, key: item.id,
is_admin: item.is_admin is_admin: item.is_admin
})); }));
console.log('options', options);
setTotalPages(res.pagination.totalPage); setTotalPages(res.pagination.totalPage);
setUserList(options); setUserList(options);
} catch (error) { } catch (error) {
@@ -56,7 +57,6 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
removeKeys: TransferKey[] removeKeys: TransferKey[]
) => { ) => {
setTargetKeys(nextTargetKeys); setTargetKeys(nextTargetKeys);
console.log('nextTargetKeys', nextTargetKeys);
const users = nextTargetKeys.map((key) => ({ id: key })); const users = nextTargetKeys.map((key) => ({ id: key }));
form.setFieldsValue({ users }); form.setFieldsValue({ users });
}; };
@@ -108,41 +108,62 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
set_public: true set_public: true
}} }}
> >
<Label>Access Scope</Label> <Label>{intl.formatMessage({ id: 'models.table.accessScope' })}</Label>
<Form.Item<AccessControlFormData> name="set_public" noStyle> <Form.Item<AccessControlFormData> name="set_public" noStyle>
<Radio.Group <Radio.Group
style={{ marginBottom: 12 }} style={{ marginBottom: 12 }}
options={[ options={[
{ label: 'All users', value: true }, {
{ label: 'Selected users', value: false } label: intl.formatMessage({ id: 'models.table.accessScope.all' }),
value: true
},
{
label: intl.formatMessage({
id: 'models.table.accessScope.selected'
}),
value: false
}
]} ]}
></Radio.Group> ></Radio.Group>
</Form.Item> </Form.Item>
{!setPublic && ( {!setPublic && (
<> <>
<Label>User Selection</Label> <Label>
{intl.formatMessage({ id: 'models.table.userSelection' })}
</Label>
<Form.Item<AccessControlFormData> name="users"> <Form.Item<AccessControlFormData> name="users">
<TransferInner <TransferInner
dataSource={userList} dataSource={userList}
targetKeys={targetKeys} targetKeys={targetKeys}
pagination={false} pagination={false}
titles={['All Users', 'Selected Users']} titles={[
intl.formatMessage({ id: 'models.table.users.all' }),
intl.formatMessage({
id: 'models.table.users.selected'
})
]}
locale={{ locale={{
notFoundContent: [ notFoundContent: [
<Empty <Empty
key="all" key="all"
description="No users found" description={intl.formatMessage({
id: 'models.table.nouserFound'
})}
image={Empty.PRESENTED_IMAGE_SIMPLE} image={Empty.PRESENTED_IMAGE_SIMPLE}
/>, />,
<Empty <Empty
key="selected" key="selected"
description="No users selected" description={intl.formatMessage({
id: 'models.table.noselected'
})}
image={Empty.PRESENTED_IMAGE_SIMPLE} image={Empty.PRESENTED_IMAGE_SIMPLE}
/> />
] ]
}} }}
showSearch={{ showSearch={{
placeholder: 'Filter by username' placeholder: intl.formatMessage({
id: 'common.filter.name'
})
}} }}
filterOption={(inputValue, item) => filterOption={(inputValue, item) =>
item.title.toLowerCase().includes(inputValue.toLowerCase()) item.title.toLowerCase().includes(inputValue.toLowerCase())
@@ -151,7 +172,9 @@ const AccessControlForm = forwardRef((props: AccessControlFormProps, ref) => {
<span className="flex-center gap-8"> <span className="flex-center gap-8">
<span>{item.title}</span> <span>{item.title}</span>
<span className="text-tertiary"> <span className="text-tertiary">
{item.is_admin ? '(Admin)' : ''} {item.is_admin
? `(${intl.formatMessage({ id: 'models.table.admin' })})`
: ''}
</span> </span>
</span> </span>
)} )}
+1 -1
View File
@@ -467,7 +467,7 @@ const Models: React.FC<ModelsProps> = ({
if (val === 'accessControl') { if (val === 'accessControl') {
setOpenAccessControlModal({ setOpenAccessControlModal({
title: 'Access Settings', title: intl.formatMessage({ id: 'models.button.accessSettings' }),
action: PageAction.EDIT, action: PageAction.EDIT,
currentData: row, currentData: row,
open: true open: true
+1 -1
View File
@@ -62,7 +62,7 @@ export const ActionList: ActionItem[] = [
icon: icons.ApiOutlined icon: icons.ApiOutlined
}, },
{ {
label: 'Access Settings', label: 'models.button.accessSettings',
key: 'accessControl', key: 'accessControl',
icon: icons.Private icon: icons.Private
}, },