@liujitcn/kratos-admin-system 0.0.28 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/package/declarations/src/api/system/base_i18n.d.ts +5 -5
- package/dist/package/declarations/src/components/DynamicI18nCell.vue.d.ts +16 -0
- package/dist/package/declarations/src/components/DynamicI18nEditor.vue.d.ts +20 -0
- package/dist/package/declarations/src/components/dynamicI18n.d.ts +38 -0
- package/dist/package/declarations/src/locales/generated.d.ts +52 -52
- package/dist/package/declarations/src/rpc/system/admin/v1/base_config.d.ts +3 -3
- package/dist/package/declarations/src/rpc/system/admin/v1/base_dict.d.ts +4 -4
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n.d.ts +23 -23
- package/dist/package/declarations/src/rpc/system/admin/v1/base_menu.d.ts +2 -2
- package/dist/package/declarations/src/rpc/system/admin/v1/code_gen.d.ts +1 -1
- package/dist/package/src/api/system/base_i18n.ts +12 -12
- package/dist/package/src/components/{DynamicTranslationCell.vue → DynamicI18nCell.vue} +50 -50
- package/dist/package/src/components/{DynamicTranslationEditor.vue → DynamicI18nEditor.vue} +45 -45
- package/dist/package/src/components/{dynamicTranslation.ts → dynamicI18n.ts} +15 -15
- package/dist/package/src/locales/README.md +1 -1
- package/dist/package/src/locales/en-US.json +15 -15
- package/dist/package/src/locales/ja-JP.json +13 -13
- package/dist/package/src/locales/zh-CN.json +13 -13
- package/dist/package/src/locales/zh-TW.json +13 -13
- package/dist/package/src/rpc/system/admin/v1/base_config.ts +3 -3
- package/dist/package/src/rpc/system/admin/v1/base_dict.ts +4 -4
- package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +23 -23
- package/dist/package/src/rpc/system/admin/v1/base_menu.ts +2 -2
- package/dist/package/src/rpc/system/admin/v1/code_gen.ts +1 -1
- package/dist/package/src/views/base/config/index.vue +38 -38
- package/dist/package/src/views/base/dict/index.vue +23 -23
- package/dist/package/src/views/base/dict/item.vue +25 -25
- package/dist/package/src/views/base/menu/index.vue +23 -23
- package/dist/package/src/views/tool/code-gen/code-preview/index.vue +9 -9
- package/dist/package/src/views/tool/code-gen/columns/index.vue +1 -1
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +8 -8
- package/dist/package/src/views/tool/code-gen/table/index.vue +7 -7
- package/package.json +2 -2
- package/dist/package/declarations/src/components/DynamicTranslationCell.vue.d.ts +0 -16
- package/dist/package/declarations/src/components/DynamicTranslationEditor.vue.d.ts +0 -20
- package/dist/package/declarations/src/components/dynamicTranslation.d.ts +0 -38
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<el-link class="dynamic-
|
|
3
|
-
<span class="dynamic-
|
|
2
|
+
<el-link class="dynamic-i18n-cell" type="primary" underline="never" @click="openDialog">
|
|
3
|
+
<span class="dynamic-i18n-cell__source">{{ displaySource || "--" }}</span>
|
|
4
4
|
</el-link>
|
|
5
5
|
|
|
6
6
|
<ProDialog
|
|
@@ -12,20 +12,20 @@
|
|
|
12
12
|
:destroy-on-close="true"
|
|
13
13
|
@closed="handleDialogClosed"
|
|
14
14
|
>
|
|
15
|
-
<el-table v-loading="loading" :data="
|
|
15
|
+
<el-table v-loading="loading" :data="i18nRows" border>
|
|
16
16
|
<el-table-column :label="t('common.field.language')" width="150">
|
|
17
17
|
<template #default="{ row }">
|
|
18
18
|
{{ getLanguageLabel(row.locale) }}
|
|
19
19
|
</template>
|
|
20
20
|
</el-table-column>
|
|
21
|
-
<el-table-column :label="t('system.base.
|
|
21
|
+
<el-table-column :label="t('system.base.i18n.field.i18ns')" min-width="360">
|
|
22
22
|
<template #default="{ row }">
|
|
23
23
|
<el-input
|
|
24
24
|
v-if="row.editing"
|
|
25
25
|
v-model="row.text"
|
|
26
|
-
:placeholder="t('system.base.
|
|
26
|
+
:placeholder="t('system.base.i18n.placeholder.text', { language: getLanguageLabel(row.locale) })"
|
|
27
27
|
/>
|
|
28
|
-
<span v-else class="dynamic-
|
|
28
|
+
<span v-else class="dynamic-i18n-dialog__text">
|
|
29
29
|
{{ row.text || t("common.value.none") }}
|
|
30
30
|
</span>
|
|
31
31
|
</template>
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
{{ t("common.action.edit") }}
|
|
37
37
|
</el-button>
|
|
38
38
|
<template v-else>
|
|
39
|
-
<el-button link type="primary" :icon="Check" :loading="row.saving" @click="
|
|
39
|
+
<el-button link type="primary" :icon="Check" :loading="row.saving" @click="saveI18n(row)">
|
|
40
40
|
{{ t("common.action.save") }}
|
|
41
41
|
</el-button>
|
|
42
42
|
<el-button link :icon="Close" :disabled="row.saving" @click="cancelEdit(row)">
|
|
@@ -58,48 +58,48 @@ import { t, useLocaleStore } from "@liujitcn/kratos-admin-core";
|
|
|
58
58
|
import { loadEnabledBaseLanguages, useEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/base_language";
|
|
59
59
|
import { defBaseI18nService } from "@liujitcn/kratos-admin-system/api/system/base_i18n";
|
|
60
60
|
import type { BaseI18n } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
|
|
61
|
-
import {
|
|
62
|
-
import { getLanguageLabel } from "./
|
|
61
|
+
import { I18nTargetType } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
|
|
62
|
+
import { getLanguageLabel } from "./dynamicI18n";
|
|
63
63
|
|
|
64
|
-
/**
|
|
65
|
-
interface
|
|
64
|
+
/** DynamicI18nCellProps 动态翻译列表单元格属性。 */
|
|
65
|
+
interface DynamicI18nCellProps {
|
|
66
66
|
/** 资源源文。 */
|
|
67
67
|
source: string;
|
|
68
68
|
/** 统一翻译表目标类型。 */
|
|
69
|
-
targetType?:
|
|
69
|
+
targetType?: I18nTargetType;
|
|
70
70
|
/** 目标资源编号。 */
|
|
71
71
|
targetId?: number;
|
|
72
72
|
/** 资源列表返回的非主语言翻译。 */
|
|
73
|
-
|
|
73
|
+
i18ns?: BaseI18n[];
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
const props = defineProps<
|
|
76
|
+
const props = defineProps<DynamicI18nCellProps>();
|
|
77
77
|
|
|
78
78
|
const { languages } = useEnabledBaseLanguages();
|
|
79
79
|
const { locale } = useLocaleStore();
|
|
80
80
|
const dialogVisible = ref(false);
|
|
81
81
|
const loading = ref(false);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
82
|
+
const i18nRows = ref<DynamicI18nRow[]>([]);
|
|
83
|
+
const i18nOverrides = ref(new Map<string, string>());
|
|
84
84
|
|
|
85
85
|
const displaySource = computed(() => {
|
|
86
|
-
const override =
|
|
86
|
+
const override = i18nOverrides.value.get(locale.value);
|
|
87
87
|
if (override) return override;
|
|
88
|
-
const
|
|
89
|
-
return
|
|
88
|
+
const i18n = props.i18ns?.find(item => item.locale === locale.value && item.name);
|
|
89
|
+
return i18n?.name || props.source;
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
watch(
|
|
93
93
|
() => [props.targetType, props.targetId],
|
|
94
94
|
() => {
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
i18nRows.value = [];
|
|
96
|
+
i18nOverrides.value.clear();
|
|
97
97
|
dialogVisible.value = false;
|
|
98
98
|
}
|
|
99
99
|
);
|
|
100
100
|
|
|
101
|
-
/**
|
|
102
|
-
interface
|
|
101
|
+
/** DynamicI18nRow 描述弹窗内可编辑的单语言翻译行。 */
|
|
102
|
+
interface DynamicI18nRow {
|
|
103
103
|
/** 翻译记录主键,新增翻译时为空。 */
|
|
104
104
|
id: number;
|
|
105
105
|
/** 语言区域。 */
|
|
@@ -117,43 +117,43 @@ interface DynamicTranslationRow {
|
|
|
117
117
|
/** openDialog 打开翻译编辑弹窗并加载当前资源译文。 */
|
|
118
118
|
async function openDialog() {
|
|
119
119
|
dialogVisible.value = true;
|
|
120
|
-
await
|
|
120
|
+
await loadI18ns();
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
/**
|
|
124
|
-
async function
|
|
123
|
+
/** loadI18ns 查询当前资源的翻译并补齐启用语言行。 */
|
|
124
|
+
async function loadI18ns() {
|
|
125
125
|
const targetType = props.targetType;
|
|
126
126
|
const targetId = props.targetId;
|
|
127
127
|
if (!targetType || !targetId) return;
|
|
128
128
|
loading.value = true;
|
|
129
129
|
try {
|
|
130
130
|
await loadEnabledBaseLanguages();
|
|
131
|
-
const rows = await
|
|
132
|
-
|
|
131
|
+
const rows = await queryI18nRows();
|
|
132
|
+
i18nRows.value = rows;
|
|
133
133
|
const missingRows = rows.filter(row => !row.text);
|
|
134
134
|
if (missingRows.length === 0) return;
|
|
135
135
|
if (!props.source) return;
|
|
136
|
-
const draft = await defBaseI18nService.
|
|
137
|
-
const
|
|
136
|
+
const draft = await defBaseI18nService.DraftBaseI18n({ source: props.source });
|
|
137
|
+
const i18ns = new Map(draft.i18ns.map(item => [item.locale, item.i18n]));
|
|
138
138
|
const results = await Promise.allSettled(
|
|
139
139
|
missingRows.map(async row => {
|
|
140
|
-
const
|
|
141
|
-
if (!
|
|
142
|
-
await defBaseI18nService.
|
|
140
|
+
const i18n = i18ns.get(row.locale);
|
|
141
|
+
if (!i18n) throw new Error("i18n not found");
|
|
142
|
+
await defBaseI18nService.UpdateBaseI18n({
|
|
143
143
|
id: row.id,
|
|
144
144
|
target_type: targetType,
|
|
145
145
|
target_id: targetId,
|
|
146
146
|
locale: row.locale,
|
|
147
|
-
name:
|
|
147
|
+
name: i18n
|
|
148
148
|
});
|
|
149
|
-
return { row, text:
|
|
149
|
+
return { row, text: i18n };
|
|
150
150
|
})
|
|
151
151
|
);
|
|
152
152
|
for (const result of results) {
|
|
153
153
|
if (result.status !== "fulfilled") continue;
|
|
154
154
|
result.value.row.text = result.value.text;
|
|
155
155
|
result.value.row.originalText = result.value.text;
|
|
156
|
-
|
|
156
|
+
i18nOverrides.value.set(result.value.row.locale, result.value.text);
|
|
157
157
|
}
|
|
158
158
|
} catch {
|
|
159
159
|
ElMessage.error(t("common.message.system_error"));
|
|
@@ -162,14 +162,14 @@ async function loadTranslations() {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/**
|
|
166
|
-
async function
|
|
167
|
-
const records = new Map((props.
|
|
165
|
+
/** queryI18nRows 将列表返回的翻译记录补齐为可编辑语言行。 */
|
|
166
|
+
async function queryI18nRows(): Promise<DynamicI18nRow[]> {
|
|
167
|
+
const records = new Map((props.i18ns ?? []).map(item => [item.locale, item]));
|
|
168
168
|
return languages.value
|
|
169
169
|
.filter(item => !item.is_primary)
|
|
170
170
|
.map(item => {
|
|
171
171
|
const record = records.get(item.language_code);
|
|
172
|
-
const text =
|
|
172
|
+
const text = i18nOverrides.value.get(item.language_code) ?? record?.name ?? "";
|
|
173
173
|
return {
|
|
174
174
|
id: record?.id ?? 0,
|
|
175
175
|
locale: item.language_code,
|
|
@@ -182,19 +182,19 @@ async function queryTranslationRows(): Promise<DynamicTranslationRow[]> {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
/** startEdit 开启指定语言的编辑状态。 */
|
|
185
|
-
function startEdit(row:
|
|
185
|
+
function startEdit(row: DynamicI18nRow) {
|
|
186
186
|
row.originalText = row.text;
|
|
187
187
|
row.editing = true;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
/** cancelEdit 恢复编辑前的内容。 */
|
|
191
|
-
function cancelEdit(row:
|
|
191
|
+
function cancelEdit(row: DynamicI18nRow) {
|
|
192
192
|
row.text = row.originalText;
|
|
193
193
|
row.editing = false;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
/**
|
|
197
|
-
async function
|
|
196
|
+
/** saveI18n 使用统一更新接口保存指定语言译文。 */
|
|
197
|
+
async function saveI18n(row: DynamicI18nRow) {
|
|
198
198
|
const targetType = props.targetType;
|
|
199
199
|
const targetId = props.targetId;
|
|
200
200
|
if (row.saving || !targetType || !targetId) return;
|
|
@@ -204,7 +204,7 @@ async function saveTranslation(row: DynamicTranslationRow) {
|
|
|
204
204
|
}
|
|
205
205
|
row.saving = true;
|
|
206
206
|
try {
|
|
207
|
-
await defBaseI18nService.
|
|
207
|
+
await defBaseI18nService.UpdateBaseI18n({
|
|
208
208
|
id: row.id,
|
|
209
209
|
target_type: targetType,
|
|
210
210
|
target_id: targetId,
|
|
@@ -214,7 +214,7 @@ async function saveTranslation(row: DynamicTranslationRow) {
|
|
|
214
214
|
ElMessage.success(t("common.message.operation_success"));
|
|
215
215
|
row.editing = false;
|
|
216
216
|
row.originalText = row.text;
|
|
217
|
-
|
|
217
|
+
i18nOverrides.value.set(row.locale, row.text);
|
|
218
218
|
} catch {
|
|
219
219
|
ElMessage.error(t("common.message.system_error"));
|
|
220
220
|
} finally {
|
|
@@ -224,25 +224,25 @@ async function saveTranslation(row: DynamicTranslationRow) {
|
|
|
224
224
|
|
|
225
225
|
/** handleDialogClosed 清理弹窗状态,避免复用旧资源译文。 */
|
|
226
226
|
function handleDialogClosed() {
|
|
227
|
-
|
|
227
|
+
i18nRows.value = [];
|
|
228
228
|
loading.value = false;
|
|
229
229
|
}
|
|
230
230
|
</script>
|
|
231
231
|
|
|
232
232
|
<style scoped>
|
|
233
|
-
.dynamic-
|
|
233
|
+
.dynamic-i18n-cell {
|
|
234
234
|
display: inline-flex;
|
|
235
235
|
align-items: center;
|
|
236
236
|
min-width: 0;
|
|
237
237
|
max-width: 100%;
|
|
238
238
|
}
|
|
239
|
-
.dynamic-
|
|
239
|
+
.dynamic-i18n-cell__source {
|
|
240
240
|
min-width: 0;
|
|
241
241
|
overflow: hidden;
|
|
242
242
|
text-overflow: ellipsis;
|
|
243
243
|
white-space: nowrap;
|
|
244
244
|
}
|
|
245
|
-
.dynamic-
|
|
245
|
+
.dynamic-i18n-dialog__text {
|
|
246
246
|
min-width: 0;
|
|
247
247
|
overflow-wrap: anywhere;
|
|
248
248
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="
|
|
3
|
-
<div class="
|
|
2
|
+
<div class="i18n-editor">
|
|
3
|
+
<div class="i18n-editor__toolbar">
|
|
4
4
|
<el-button
|
|
5
5
|
type="primary"
|
|
6
6
|
size="small"
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
:disabled="!canTranslate"
|
|
10
10
|
@click="handleBatchTranslate"
|
|
11
11
|
>
|
|
12
|
-
{{ t("system.base.
|
|
12
|
+
{{ t("system.base.i18n.action.batch_translate") }}
|
|
13
13
|
</el-button>
|
|
14
14
|
</div>
|
|
15
|
-
<div v-for="item in localValues" :key="item.locale" class="
|
|
16
|
-
<div class="
|
|
15
|
+
<div v-for="item in localValues" :key="item.locale" class="i18n-editor__row">
|
|
16
|
+
<div class="i18n-editor__heading">
|
|
17
17
|
<span>{{ getLanguageLabel(item.locale) }}</span>
|
|
18
18
|
</div>
|
|
19
|
-
<div class="
|
|
19
|
+
<div class="i18n-editor__control" :class="{ 'i18n-editor__control--multiline': multiline }">
|
|
20
20
|
<el-input
|
|
21
21
|
:model-value="item.text"
|
|
22
22
|
:maxlength="maxlength"
|
|
@@ -24,11 +24,11 @@
|
|
|
24
24
|
:rows="multiline ? 6 : undefined"
|
|
25
25
|
:disabled="translating || translatingLocale === item.locale"
|
|
26
26
|
show-word-limit
|
|
27
|
-
:placeholder="t('system.base.
|
|
27
|
+
:placeholder="t('system.base.i18n.placeholder.text', { language: getLanguageLabel(item.locale) })"
|
|
28
28
|
@update:model-value="value => updateText(item.locale, value)"
|
|
29
29
|
/>
|
|
30
30
|
<el-button
|
|
31
|
-
class="
|
|
31
|
+
class="i18n-editor__translate"
|
|
32
32
|
link
|
|
33
33
|
type="primary"
|
|
34
34
|
size="small"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
:disabled="!props.source || Boolean(item.text) || translating || Boolean(translatingLocale)"
|
|
38
38
|
@click="handleTranslate(item.locale)"
|
|
39
39
|
>
|
|
40
|
-
{{ t("system.base.
|
|
40
|
+
{{ t("system.base.i18n.action.translate") }}
|
|
41
41
|
</el-button>
|
|
42
42
|
</div>
|
|
43
43
|
</div>
|
|
@@ -50,26 +50,26 @@ import { ElMessage } from "element-plus";
|
|
|
50
50
|
import { Promotion } from "@element-plus/icons-vue";
|
|
51
51
|
import { t, useLocaleStore } from "@liujitcn/kratos-admin-core";
|
|
52
52
|
import { defBaseI18nService } from "@liujitcn/kratos-admin-system/api/system/base_i18n";
|
|
53
|
-
import { getLanguageLabel, type
|
|
53
|
+
import { getLanguageLabel, type DynamicI18nValue } from "./dynamicI18n";
|
|
54
54
|
|
|
55
|
-
/**
|
|
56
|
-
interface
|
|
57
|
-
modelValue:
|
|
55
|
+
/** DynamicI18nEditorProps 动态翻译编辑器属性。 */
|
|
56
|
+
interface DynamicI18nEditorProps {
|
|
57
|
+
modelValue: DynamicI18nValue[];
|
|
58
58
|
/** 当前语言源文本。 */
|
|
59
59
|
source?: string;
|
|
60
60
|
maxlength?: number;
|
|
61
61
|
multiline?: boolean;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const props = withDefaults(defineProps<
|
|
64
|
+
const props = withDefaults(defineProps<DynamicI18nEditorProps>(), {
|
|
65
65
|
source: "",
|
|
66
66
|
maxlength: 100,
|
|
67
67
|
multiline: false
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
const { locale: currentLocale } = useLocaleStore();
|
|
71
|
-
const emit = defineEmits<{ "update:modelValue": [value:
|
|
72
|
-
const localValues = ref<
|
|
71
|
+
const emit = defineEmits<{ "update:modelValue": [value: DynamicI18nValue[]] }>();
|
|
72
|
+
const localValues = ref<DynamicI18nValue[]>([]);
|
|
73
73
|
const translating = ref(false);
|
|
74
74
|
const translatingLocale = ref("");
|
|
75
75
|
const canTranslate = computed(() => Boolean(props.source) && localValues.value.some(item => !item.text && item.locale !== currentLocale.value));
|
|
@@ -83,7 +83,7 @@ watch(
|
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
/** updateText 更新指定语言的人工译文。 */
|
|
86
|
-
function updateText(locale:
|
|
86
|
+
function updateText(locale: DynamicI18nValue["locale"], text: string) {
|
|
87
87
|
const values = localValues.value.map(item => (item.locale === locale ? { ...item, text } : item));
|
|
88
88
|
localValues.value = values;
|
|
89
89
|
emit("update:modelValue", values);
|
|
@@ -95,29 +95,29 @@ async function handleBatchTranslate() {
|
|
|
95
95
|
translating.value = true;
|
|
96
96
|
try {
|
|
97
97
|
const pending = localValues.value.filter(item => !item.text && item.locale !== currentLocale.value);
|
|
98
|
-
const response = await defBaseI18nService.
|
|
99
|
-
const
|
|
100
|
-
const failed = pending.filter(item => !
|
|
101
|
-
if (
|
|
98
|
+
const response = await defBaseI18nService.DraftBaseI18n({ source: props.source });
|
|
99
|
+
const i18ns = new Map(response.i18ns.map(item => [item.locale, item.i18n]));
|
|
100
|
+
const failed = pending.filter(item => !i18ns.has(item.locale)).length;
|
|
101
|
+
if (i18ns.size) {
|
|
102
102
|
const values = localValues.value.map(item => {
|
|
103
|
-
const
|
|
104
|
-
return
|
|
103
|
+
const i18n = i18ns.get(item.locale);
|
|
104
|
+
return i18n === undefined ? item : { ...item, text: i18n };
|
|
105
105
|
});
|
|
106
106
|
localValues.value = values;
|
|
107
107
|
emit("update:modelValue", values);
|
|
108
108
|
}
|
|
109
109
|
if (failed) {
|
|
110
110
|
ElMessage.warning(
|
|
111
|
-
t("system.base.
|
|
112
|
-
success:
|
|
111
|
+
t("system.base.i18n.message.batch_translate_partial", {
|
|
112
|
+
success: i18ns.size,
|
|
113
113
|
failed
|
|
114
114
|
})
|
|
115
115
|
);
|
|
116
116
|
} else {
|
|
117
|
-
ElMessage.success(t("system.base.
|
|
117
|
+
ElMessage.success(t("system.base.i18n.message.batch_translate_success", { count: i18ns.size }));
|
|
118
118
|
}
|
|
119
119
|
} catch {
|
|
120
|
-
ElMessage.error(t("system.base.
|
|
120
|
+
ElMessage.error(t("system.base.i18n.message.batch_translate_failed"));
|
|
121
121
|
} finally {
|
|
122
122
|
translating.value = false;
|
|
123
123
|
}
|
|
@@ -129,15 +129,15 @@ async function handleTranslate(locale: string) {
|
|
|
129
129
|
if (!item || item.text || item.locale === currentLocale.value || !props.source || translating.value || translatingLocale.value) return;
|
|
130
130
|
translatingLocale.value = locale;
|
|
131
131
|
try {
|
|
132
|
-
const response = await defBaseI18nService.
|
|
133
|
-
const
|
|
134
|
-
if (!
|
|
135
|
-
const values = localValues.value.map(value => (value.locale === locale ? { ...value, text:
|
|
132
|
+
const response = await defBaseI18nService.DraftBaseI18n({ source: props.source, locale });
|
|
133
|
+
const i18n = response.i18ns.find(item => item.locale === locale)?.i18n;
|
|
134
|
+
if (!i18n) throw new Error("i18n not found");
|
|
135
|
+
const values = localValues.value.map(value => (value.locale === locale ? { ...value, text: i18n } : value));
|
|
136
136
|
localValues.value = values;
|
|
137
137
|
emit("update:modelValue", values);
|
|
138
|
-
ElMessage.success(t("system.base.
|
|
138
|
+
ElMessage.success(t("system.base.i18n.message.translate_success", { language: getLanguageLabel(locale) }));
|
|
139
139
|
} catch {
|
|
140
|
-
ElMessage.error(t("system.base.
|
|
140
|
+
ElMessage.error(t("system.base.i18n.message.translate_failed", { language: getLanguageLabel(locale) }));
|
|
141
141
|
} finally {
|
|
142
142
|
translatingLocale.value = "";
|
|
143
143
|
}
|
|
@@ -146,51 +146,51 @@ async function handleTranslate(locale: string) {
|
|
|
146
146
|
</script>
|
|
147
147
|
|
|
148
148
|
<style scoped>
|
|
149
|
-
.
|
|
149
|
+
.i18n-editor {
|
|
150
150
|
display: flex;
|
|
151
151
|
flex-direction: column;
|
|
152
152
|
gap: 14px;
|
|
153
153
|
width: 100%;
|
|
154
154
|
}
|
|
155
|
-
.
|
|
155
|
+
.i18n-editor__toolbar {
|
|
156
156
|
display: flex;
|
|
157
157
|
justify-content: flex-end;
|
|
158
158
|
}
|
|
159
|
-
.
|
|
159
|
+
.i18n-editor__row {
|
|
160
160
|
display: flex;
|
|
161
161
|
flex-direction: column;
|
|
162
162
|
gap: 6px;
|
|
163
163
|
}
|
|
164
|
-
.
|
|
165
|
-
.
|
|
164
|
+
.i18n-editor__heading,
|
|
165
|
+
.i18n-editor__control {
|
|
166
166
|
display: flex;
|
|
167
167
|
gap: 8px;
|
|
168
168
|
align-items: center;
|
|
169
169
|
}
|
|
170
|
-
.
|
|
170
|
+
.i18n-editor__heading {
|
|
171
171
|
color: var(--el-text-color-regular);
|
|
172
172
|
}
|
|
173
|
-
.
|
|
173
|
+
.i18n-editor__control {
|
|
174
174
|
position: relative;
|
|
175
175
|
width: 100%;
|
|
176
176
|
}
|
|
177
|
-
.
|
|
177
|
+
.i18n-editor__control :deep(.el-input) {
|
|
178
178
|
flex: 1;
|
|
179
179
|
}
|
|
180
|
-
.
|
|
180
|
+
.i18n-editor__control--multiline :deep(.el-textarea__inner) {
|
|
181
181
|
padding-right: 78px;
|
|
182
182
|
}
|
|
183
|
-
.
|
|
183
|
+
.i18n-editor__control:not(.i18n-editor__control--multiline) :deep(.el-input__suffix) {
|
|
184
184
|
padding-right: 78px;
|
|
185
185
|
}
|
|
186
|
-
.
|
|
186
|
+
.i18n-editor__translate {
|
|
187
187
|
position: absolute;
|
|
188
188
|
z-index: 1;
|
|
189
189
|
top: 50%;
|
|
190
190
|
right: 8px;
|
|
191
191
|
transform: translateY(-50%);
|
|
192
192
|
}
|
|
193
|
-
.
|
|
193
|
+
.i18n-editor__control--multiline .i18n-editor__translate {
|
|
194
194
|
top: 8px;
|
|
195
195
|
transform: none;
|
|
196
196
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { getEnabledBaseLanguages } from "@liujitcn/kratos-admin-system/api/system/base_language";
|
|
2
|
-
import type {
|
|
2
|
+
import type { I18nTargetType } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_i18n";
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
export interface
|
|
4
|
+
/** DynamicI18nValue 动态资源单语言翻译编辑状态。 */
|
|
5
|
+
export interface DynamicI18nValue {
|
|
6
6
|
/** 语言代码。 */
|
|
7
7
|
locale: string;
|
|
8
8
|
text: string;
|
|
9
9
|
id: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
export interface
|
|
14
|
-
target_type?:
|
|
12
|
+
/** DynamicI18nRecord 描述三类动态资源翻译共有字段。 */
|
|
13
|
+
export interface DynamicI18nRecord extends Omit<DynamicI18nValue, "text"> {
|
|
14
|
+
target_type?: I18nTargetType;
|
|
15
15
|
target_id?: number;
|
|
16
16
|
name?: string;
|
|
17
17
|
/** 兼容旧接口缓存中的字段名。 */
|
|
@@ -40,12 +40,12 @@ export function getLanguageLabel(locale: string): string {
|
|
|
40
40
|
return language?.native_name || language?.language_name || locale;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
44
|
-
export function
|
|
45
|
-
records:
|
|
43
|
+
/** normalizeDynamicI18ns 将后端翻译记录补齐为全部非主语言编辑状态。 */
|
|
44
|
+
export function normalizeDynamicI18ns(
|
|
45
|
+
records: DynamicI18nRecord[] | undefined,
|
|
46
46
|
_textField: "title" | "name" | "label",
|
|
47
47
|
locales: string[] = getEditableLanguageOptions().map(item => item.value)
|
|
48
|
-
):
|
|
48
|
+
): DynamicI18nValue[] {
|
|
49
49
|
return locales.map(locale => {
|
|
50
50
|
const record = records?.find(item => item.locale === locale);
|
|
51
51
|
return {
|
|
@@ -56,11 +56,11 @@ export function normalizeDynamicTranslations(
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/**
|
|
60
|
-
export function
|
|
61
|
-
values:
|
|
62
|
-
targetType:
|
|
59
|
+
/** serializeDynamicI18ns 将编辑状态转换回后端资源翻译结构。 */
|
|
60
|
+
export function serializeDynamicI18ns(
|
|
61
|
+
values: DynamicI18nValue[],
|
|
62
|
+
targetType: I18nTargetType,
|
|
63
63
|
targetId: number
|
|
64
|
-
): Array<{ id: number; target_type:
|
|
64
|
+
): Array<{ id: number; target_type: I18nTargetType; target_id: number; locale: string; name: string }> {
|
|
65
65
|
return values.map(({ id, locale, text }) => ({ id, target_type: targetType, target_id: targetId, locale, name: text }));
|
|
66
66
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| `system.profile.*` | 个人资料、安全设置和用户中心导航。 | `src/views/profile/**`、`src/module.ts` 的用户菜单。 |
|
|
18
18
|
| `system.base.*` | 区域、配置、部门、字典、岗位、角色、租户、用户等基础管理页面。 | `src/views/base/**`。 |
|
|
19
19
|
| `system.code.gen.*` | 代码生成配置、预览、列配置和多语言编辑。 | `src/views/tool/code-gen/**`。 |
|
|
20
|
-
| `system.
|
|
20
|
+
| `system.i18n.*` | 动态翻译编辑、草稿生成和语言包状态。 | `src/components/DynamicI18nEditor.vue`、代码生成语言编辑组件。 |
|
|
21
21
|
|
|
22
22
|
语言包由生成的 `generated.ts` 传入 `systemAdminModule.messages`,由 core 的 `registerLocaleMessages` 校验语言键集合、命名空间和占位符后注册到 Vue I18n;业务代码统一使用 `t('system....')` 或 `useLocaleStore()` 读取。
|
|
23
23
|
|
|
@@ -248,17 +248,17 @@
|
|
|
248
248
|
"system.base.user.message.confirm_delete_selected": "Delete the selected users?",
|
|
249
249
|
"system.base.user.message.delete_success": "User deleted",
|
|
250
250
|
"system.base.user.message.delete_canceled": "User deletion canceled",
|
|
251
|
-
"system.base.
|
|
252
|
-
"system.base.
|
|
253
|
-
"system.base.
|
|
254
|
-
"system.base.
|
|
255
|
-
"system.base.
|
|
256
|
-
"system.base.
|
|
257
|
-
"system.base.
|
|
258
|
-
"system.base.
|
|
259
|
-
"system.base.
|
|
260
|
-
"system.base.
|
|
261
|
-
"system.base.
|
|
251
|
+
"system.base.i18n.field.i18ns": "I18ns",
|
|
252
|
+
"system.base.i18n.field.name_i18ns": "Name i18ns",
|
|
253
|
+
"system.base.i18n.field.value_i18ns": "Value i18ns",
|
|
254
|
+
"system.base.i18n.placeholder.text": "Enter the {language} i18n",
|
|
255
|
+
"system.base.i18n.action.translate": "Translate",
|
|
256
|
+
"system.base.i18n.action.batch_translate": "Translate all",
|
|
257
|
+
"system.base.i18n.message.translate_success": "{language} translated",
|
|
258
|
+
"system.base.i18n.message.translate_failed": "Failed to translate {language}",
|
|
259
|
+
"system.base.i18n.message.batch_translate_success": "Translated {count} languages",
|
|
260
|
+
"system.base.i18n.message.batch_translate_partial": "Translated {success} languages, {failed} failed",
|
|
261
|
+
"system.base.i18n.message.batch_translate_failed": "Batch i18n failed",
|
|
262
262
|
"system.base.menu.title.list": "Menus",
|
|
263
263
|
"system.base.menu.action.create": "Create menu",
|
|
264
264
|
"system.base.menu.action.edit": "Edit menu",
|
|
@@ -626,14 +626,14 @@
|
|
|
626
626
|
"system.code.gen.validation.left_tree_parent_required": "Select the tree parent column",
|
|
627
627
|
"system.code.gen.validation.left_tree_label_required": "Select the tree label column",
|
|
628
628
|
"system.code.gen.validation.left_tree_value_required": "Select the tree value column",
|
|
629
|
-
"system.code.gen.i18n.field.
|
|
629
|
+
"system.code.gen.i18n.field.i18ns": "Non-primary language descriptions",
|
|
630
630
|
"system.code.gen.i18n.action.edit_column": "Edit non-primary language field descriptions",
|
|
631
631
|
"system.code.gen.i18n.status.complete": "Complete",
|
|
632
632
|
"system.code.gen.i18n.status.missing": "Missing",
|
|
633
633
|
"system.code.gen.i18n.message.missing": "Required before generation: {locales}",
|
|
634
634
|
"system.code.gen.i18n.placeholder.comment": "Chinese source: {source}",
|
|
635
635
|
"system.code.gen.i18n.placeholder.left_tree_comment": "Chinese tree source: {source}",
|
|
636
|
-
"system.code.gen.i18n.tooltip.table": "Maintain non-primary language descriptions used by generated pages, menus, and locale files. Incomplete drafts can be saved, but generation requires all
|
|
636
|
+
"system.code.gen.i18n.tooltip.table": "Maintain non-primary language descriptions used by generated pages, menus, and locale files. Incomplete drafts can be saved, but generation requires all i18ns.",
|
|
637
637
|
"system.code.gen.table.title.create": "Create code generation table",
|
|
638
638
|
"system.code.gen.table.title.edit": "Edit code generation table",
|
|
639
639
|
"system.code.gen.table.field.name": "Business table",
|
|
@@ -685,7 +685,7 @@
|
|
|
685
685
|
"system.code.gen.table.tooltip.left_tree_lazy": "Request children only when a node is expanded.",
|
|
686
686
|
"system.code.gen.table.tooltip.gen_backend": "Generate Proto, backend Biz, Service, and registration code.",
|
|
687
687
|
"system.code.gen.table.tooltip.gen_frontend": "Generate the frontend API, pages, and locale files for all supported languages.",
|
|
688
|
-
"system.code.gen.table.tooltip.gen_sql": "Synchronize page menus, button permissions, and
|
|
688
|
+
"system.code.gen.table.tooltip.gen_sql": "Synchronize page menus, button permissions, and i18ns for all supported languages.",
|
|
689
689
|
"system.code.gen.table.tooltip.status": "Disabled configurations are read-only. Successful generation sets the status to generated.",
|
|
690
690
|
"system.code.gen.table.tooltip.remark": "Visible to maintainers only and not used for generated names.",
|
|
691
691
|
"system.code.gen.table.value.disabled_module": "{module} (disabled)",
|
|
@@ -881,7 +881,7 @@
|
|
|
881
881
|
"system.code.gen.preview.message.create_success": "Created",
|
|
882
882
|
"system.code.gen.preview.message.update_success": "Updated",
|
|
883
883
|
"system.code.gen.preview.message.delete_success": "Deleted",
|
|
884
|
-
"system.code.gen.preview.message.
|
|
884
|
+
"system.code.gen.preview.message.missing_i18ns": "Complete these i18ns before generation: {items}",
|
|
885
885
|
"system.code.gen.preview.value.list_separator": ", ",
|
|
886
886
|
"system.project.document.title.navigation": "Project document navigation",
|
|
887
887
|
"system.project.document.placeholder.search": "Search projects or paths",
|