vome-core 0.1.86 → 0.1.88

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.
Files changed (63) hide show
  1. package/dist/admin/components/ui/avatar/index.js +1 -1
  2. package/dist/admin/components/ui/checkbox/index.js +1 -1
  3. package/dist/admin/components/ui/dialog/index.js +1 -1
  4. package/dist/admin/components/ui/dropdown-menu/index.js +1 -1
  5. package/dist/admin/components/ui/input/index.js +1 -1
  6. package/dist/admin/components/ui/label/index.js +1 -1
  7. package/dist/admin/components/ui/scroll-area/index.js +1 -1
  8. package/dist/admin/components/ui/select/index.js +1 -1
  9. package/dist/admin/components/ui/separator/index.js +1 -1
  10. package/dist/admin/components/ui/sheet/index.js +1 -1
  11. package/dist/admin/components/ui/sonner/index.js +1 -1
  12. package/dist/admin/components/ui/table/index.js +1 -1
  13. package/dist/admin/components/ui/tabs/index.js +1 -1
  14. package/dist/admin/components/ui/tooltip/index.js +1 -1
  15. package/dist/admin/crud/components/vm-richtext-extensions.js +1 -1
  16. package/dist/admin/crud/config.js +1 -1
  17. package/dist/admin/crud/confirm.js +1 -1
  18. package/dist/admin/crud/crud-page-request.js +1 -1
  19. package/dist/admin/crud/data-i18n.d.ts +39 -0
  20. package/dist/admin/crud/data-i18n.js +1 -0
  21. package/dist/admin/crud/dict.js +1 -1
  22. package/dist/admin/crud/export-fetch.js +1 -1
  23. package/dist/admin/crud/key.js +1 -1
  24. package/dist/admin/crud/plugins.js +1 -1
  25. package/dist/admin/crud/span.js +1 -1
  26. package/dist/admin/crud/style.js +1 -1
  27. package/dist/admin/crud/validate.js +1 -1
  28. package/dist/admin/crud/vm-crud.vue +66 -0
  29. package/dist/admin/crud/vm-data-i18n-btn.vue +209 -72
  30. package/dist/admin/crud/vm-data-i18n-lang.vue +176 -0
  31. package/dist/admin/crud/vm-table.vue +188 -5
  32. package/dist/admin/crud/vm-toolbar.vue +80 -47
  33. package/dist/admin/directives/perm.js +1 -1
  34. package/dist/admin/hooks/useUpload.js +1 -1
  35. package/dist/admin/lib/browser.js +1 -1
  36. package/dist/admin/lib/cn.js +1 -1
  37. package/dist/admin/lib/dialog-float.js +1 -1
  38. package/dist/admin/lib/export-excel.js +1 -1
  39. package/dist/admin/lib/import-excel.js +1 -1
  40. package/dist/admin/lib/json.js +1 -1
  41. package/dist/admin/lib/menu.js +1 -1
  42. package/dist/admin/lib/micro-locale.js +1 -1
  43. package/dist/admin/lib/micro-theme.js +1 -1
  44. package/dist/admin/lib/setup-crud-defaults.js +1 -1
  45. package/dist/admin/lib/shell.js +1 -1
  46. package/dist/admin/lib/theme-color-options.js +1 -1
  47. package/dist/admin/lib/tree.js +1 -1
  48. package/dist/admin/lib/upload.js +1 -1
  49. package/dist/admin/lib/video-frame.js +1 -1
  50. package/dist/admin/router/create.js +1 -1
  51. package/dist/admin/router/guards.js +1 -1
  52. package/dist/admin/themes/dark.js +1 -1
  53. package/dist/admin/themes/index.js +1 -1
  54. package/dist/admin/themes/light.js +1 -1
  55. package/dist/index.js +1 -1
  56. package/dist/server/index.js +1 -1
  57. package/dist/shared/datetime-picker.js +1 -1
  58. package/dist/shared/excel.js +1 -1
  59. package/dist/shared/index.js +1 -1
  60. package/dist/shared/locale-json.js +1 -1
  61. package/dist/shared/tree.js +1 -1
  62. package/package.json +1 -1
  63. package/typings/admin/comm/crud.ts +8 -1
@@ -1,55 +1,116 @@
1
1
  <template>
2
- <div v-if="visible">
2
+ <div v-if="visible" class="vm-data-i18n-btn">
3
3
  <button
4
4
  type="button"
5
5
  class="vm-crud-toolbar__btn is-ghost"
6
6
  :disabled="disabled || submitting"
7
- @click="open = true"
7
+ @click="openDialog"
8
8
  >
9
9
  <i class="ri-translate-2" />
10
- {{ text || 'AI 翻译' }}
10
+ {{ text || '翻译' }}
11
11
  </button>
12
12
 
13
+ <!-- 与 vm-upsert 同壳:24 栅格、字段默认 span 12 -->
13
14
  <Dialog :open="open" @update:open="onOpenChange">
14
- <DialogContent shell="crud" class="vm-data-i18n-btn__dialog">
15
+ <DialogContent
16
+ shell="crud"
17
+ :show-close-button="false"
18
+ class="vm-crud-upsert vm-data-i18n-btn__dialog"
19
+ @pointer-down-outside="preventDialogOutsideClose"
20
+ @focus-outside="preventDialogOutsideClose"
21
+ @interact-outside="preventDialogOutsideClose"
22
+ >
15
23
  <DialogHeader class="vm-crud-shell__head">
16
- <DialogTitle class="vm-crud-shell__title">业务数据 AI 翻译</DialogTitle>
24
+ <DialogTitle class="vm-crud-shell__title">业务数据翻译</DialogTitle>
25
+ <DialogClose class="vm-crud-shell__close">
26
+ <i class="ri-close-line" />
27
+ <span class="sr-only">Close</span>
28
+ </DialogClose>
17
29
  </DialogHeader>
18
- <div class="vm-data-i18n-btn__body">
19
- <p v-if="tableName" class="vm-data-i18n-btn__hint">
20
- 业务表:<code>{{ tableName }}</code>
21
- </p>
22
- <label class="vm-data-i18n-btn__field">
23
- <span>目标语种</span>
30
+
31
+ <div class="vm-crud-upsert__body">
32
+ <div
33
+ class="vm-crud-upsert__field"
34
+ :style="{ gridColumn: `span ${fieldSpan}` }"
35
+ >
36
+ <Label class="vm-crud-upsert__label">业务表</Label>
37
+ <Input
38
+ class="vm-crud-upsert__control"
39
+ :model-value="tableName"
40
+ disabled
41
+ />
42
+ </div>
43
+
44
+ <div
45
+ class="vm-crud-upsert__field"
46
+ :style="{ gridColumn: `span ${fieldSpan}` }"
47
+ >
48
+ <Label class="vm-crud-upsert__label">
49
+ 目标语种
50
+ <span class="vm-crud-upsert__req">*</span>
51
+ </Label>
24
52
  <vm-select
25
53
  v-model="form.langCode"
54
+ class="vm-crud-upsert__control"
26
55
  :options="langOptions"
27
56
  :refresh-on-change="false"
28
57
  width="100%"
29
58
  placeholder="选择语种"
30
59
  />
31
- </label>
32
- <label class="vm-data-i18n-btn__field">
33
- <span>模式</span>
60
+ </div>
61
+
62
+ <div
63
+ class="vm-crud-upsert__field"
64
+ :style="{ gridColumn: `span ${fieldSpan}` }"
65
+ >
66
+ <Label class="vm-crud-upsert__label">
67
+ AI 模型
68
+ <span class="vm-crud-upsert__req">*</span>
69
+ </Label>
70
+ <vm-select
71
+ v-model="form.model"
72
+ class="vm-crud-upsert__control"
73
+ :options="modelOptions"
74
+ :refresh-on-change="false"
75
+ width="100%"
76
+ placeholder="请选择模型"
77
+ />
78
+ </div>
79
+
80
+ <div
81
+ class="vm-crud-upsert__field"
82
+ :style="{ gridColumn: `span ${fieldSpan}` }"
83
+ >
84
+ <Label class="vm-crud-upsert__label">
85
+ 模式
86
+ <span class="vm-crud-upsert__req">*</span>
87
+ </Label>
34
88
  <vm-select
35
89
  v-model="form.mode"
90
+ class="vm-crud-upsert__control"
36
91
  :options="modeOptions"
37
92
  :refresh-on-change="false"
38
93
  width="100%"
39
94
  />
40
- </label>
95
+ </div>
41
96
  </div>
97
+
42
98
  <DialogFooter class="vm-crud-shell__foot">
43
- <button type="button" class="vm-crud-toolbar__btn is-ghost" @click="open = false">
99
+ <button
100
+ type="button"
101
+ class="vm-crud-shell__btn is-ghost"
102
+ :disabled="submitting"
103
+ @click="open = false"
104
+ >
44
105
  取消
45
106
  </button>
46
107
  <button
47
108
  type="button"
48
- class="vm-crud-toolbar__btn is-primary"
49
- :disabled="submitting"
109
+ class="vm-crud-shell__btn is-primary"
110
+ :disabled="submitting || !form.model || !form.langCode"
50
111
  @click="runTranslate"
51
112
  >
52
- {{ submitting ? '翻译中…' : '开始翻译' }}
113
+ {{ submitting ? '翻译中…' : '翻译' }}
53
114
  </button>
54
115
  </DialogFooter>
55
116
  </DialogContent>
@@ -60,11 +121,15 @@
60
121
  <script setup lang="ts">
61
122
  import { computed, reactive, ref, watch } from 'vue'
62
123
  import { toast } from 'vue-sonner'
63
- import { findEpsEntity } from '../lib/eps'
124
+ import { preventDialogOutsideClose } from '../lib/dialog-float'
64
125
  import { useVome } from '../hooks/useVome'
65
126
  import { useCrud } from './useCrud'
66
127
  import VmSelect from './components/vm-select.vue'
67
- import type { EpsEntity } from '../../../typings/routing/eps'
128
+ import { resolveFormSpan, useFormCols } from './span'
129
+ import {
130
+ canUseDataI18n,
131
+ dataPackFromService,
132
+ } from './data-i18n'
68
133
 
69
134
  defineOptions({ name: 'vm-data-i18n-btn' })
70
135
 
@@ -85,9 +150,14 @@ const { service } = useVome()
85
150
  const open = ref(false)
86
151
  const submitting = ref(false)
87
152
  const langOptions = ref<Array<{ label: string; value: string }>>([])
153
+ const modelOptions = ref<Array<{ label: string; value: string }>>([])
154
+ const formCols = useFormCols()
155
+ /** 与 skill 约定一致:表单字段默认 span 12(半宽两列) */
156
+ const fieldSpan = computed(() => resolveFormSpan(12, formCols.value))
88
157
 
89
158
  const form = reactive({
90
159
  langCode: 'en-US',
160
+ model: '',
91
161
  mode: 'incremental' as 'incremental' | 'full',
92
162
  })
93
163
 
@@ -96,41 +166,34 @@ const modeOptions = [
96
166
  { label: '全量覆盖', value: 'full' },
97
167
  ]
98
168
 
99
- const epsEntity = computed(() => {
100
- const ns = (crud.service as { namespace?: string } | undefined)?.namespace
101
- if (!ns) return undefined
102
- return findEpsEntity(`/${ns}`) as (EpsEntity & { dataI18n?: boolean }) | undefined
103
- })
104
-
105
- function resolveTableName(entity?: EpsEntity): string {
106
- if (props.tableName?.trim()) return props.tableName.trim()
107
- if (!entity) return ''
108
- const cols = [...(entity.columns || []), ...(entity.pageColumns || [])]
109
- for (const col of cols) {
110
- const table = String(col.table || '').trim()
111
- if (table) return table
112
- }
113
- return ''
114
- }
115
-
116
- const tableName = computed(() => resolveTableName(epsEntity.value))
169
+ const meta = computed(() =>
170
+ canUseDataI18n(crud.service, service, props.tableName),
171
+ )
117
172
 
118
- const visible = computed(() => {
119
- if (!epsEntity.value?.dataI18n) return false
120
- if (!tableName.value) return false
121
- const dataPack = (service as { i18n?: { dataPack?: { translateTable?: unknown } } })
122
- .i18n?.dataPack
123
- return Boolean(dataPack?.translateTable)
124
- })
173
+ const tableName = computed(() => meta.value.tableName)
174
+ const visible = computed(() => meta.value.ok)
125
175
 
126
176
  watch(open, (v) => {
127
- if (v) void loadLangs()
177
+ if (v) void loadDialogData()
128
178
  })
129
179
 
130
180
  function onOpenChange(v: boolean) {
131
181
  open.value = v
132
182
  }
133
183
 
184
+ function openDialog() {
185
+ if (props.disabled || submitting.value) return
186
+ open.value = true
187
+ }
188
+
189
+ async function loadDialogData() {
190
+ await Promise.all([loadLangs(), loadModels()])
191
+ const viewing = crud.dataI18nLangCode?.value
192
+ if (viewing && langOptions.value.some((o) => o.value === viewing)) {
193
+ form.langCode = viewing
194
+ }
195
+ }
196
+
134
197
  async function loadLangs() {
135
198
  const langSvc = (service as { i18n?: { lang?: { list?: (q: unknown) => Promise<unknown> } } })
136
199
  .i18n?.lang
@@ -157,6 +220,27 @@ async function loadLangs() {
157
220
  }
158
221
  }
159
222
 
223
+ async function loadModels() {
224
+ const dataPack = dataPackFromService(service)
225
+ if (!dataPack?.chatModels) {
226
+ modelOptions.value = []
227
+ form.model = ''
228
+ return
229
+ }
230
+ try {
231
+ const list = (await dataPack.chatModels()) as Array<{ code?: string }>
232
+ modelOptions.value = (list || [])
233
+ .filter((m) => m.code)
234
+ .map((m) => ({ label: String(m.code), value: String(m.code) }))
235
+ if (!modelOptions.value.some((o) => o.value === form.model)) {
236
+ form.model = modelOptions.value[0]?.value || ''
237
+ }
238
+ } catch {
239
+ modelOptions.value = []
240
+ form.model = ''
241
+ }
242
+ }
243
+
160
244
  async function runTranslate() {
161
245
  if (submitting.value || props.disabled) return
162
246
  const table = tableName.value
@@ -168,13 +252,11 @@ async function runTranslate() {
168
252
  toast.error('请选择非中文目标语种')
169
253
  return
170
254
  }
171
- const dataPack = (service as {
172
- i18n?: {
173
- dataPack?: {
174
- translateTable?: (body: Record<string, unknown>) => Promise<unknown>
175
- }
176
- }
177
- }).i18n?.dataPack
255
+ if (!form.model) {
256
+ toast.error('请选择 AI 模型')
257
+ return
258
+ }
259
+ const dataPack = dataPackFromService(service)
178
260
  if (!dataPack?.translateTable) {
179
261
  toast.error('未配置业务翻译服务')
180
262
  return
@@ -185,6 +267,7 @@ async function runTranslate() {
185
267
  tableName: table,
186
268
  langCode: form.langCode,
187
269
  mode: form.mode,
270
+ model: form.model,
188
271
  })) as { skipped?: boolean; message?: string; rowCount?: number }
189
272
  if (res?.skipped) {
190
273
  toast.message(res.message || '已跳过')
@@ -192,6 +275,12 @@ async function runTranslate() {
192
275
  toast.success(`翻译完成,共 ${res?.rowCount ?? 0} 条`)
193
276
  }
194
277
  open.value = false
278
+ if (crud.dataI18nLangCode) {
279
+ crud.dataI18nLangCode.value = form.langCode
280
+ }
281
+ if (crud.trashMode.value !== 'dataI18n') {
282
+ crud.trashMode.value = 'dataI18n'
283
+ }
195
284
  await crud.refresh()
196
285
  } catch (e) {
197
286
  toast.error(e instanceof Error ? e.message : '翻译失败')
@@ -202,33 +291,81 @@ async function runTranslate() {
202
291
  </script>
203
292
 
204
293
  <style lang="scss" scoped>
205
- .vm-data-i18n-btn__dialog {
206
- width: min(480px, calc(100vw - 32px));
294
+ .vm-data-i18n-btn {
295
+ display: inline-flex;
207
296
  }
208
297
 
209
- .vm-data-i18n-btn__body {
210
- display: flex;
211
- flex-direction: column;
212
- gap: 14px;
213
- padding: 8px 22px 16px;
298
+ .vm-crud-toolbar__btn {
299
+ display: inline-flex;
300
+ height: 36px;
301
+ align-items: center;
302
+ gap: 4px;
303
+ padding: 0 14px;
304
+ border: none;
305
+ border-radius: 12px;
306
+ font-size: 13px;
307
+ font-weight: 600;
308
+ cursor: pointer;
309
+ background: var(--muted);
310
+ color: var(--foreground);
311
+ transition: background 0.15s ease, opacity 0.15s ease;
312
+
313
+ &:disabled {
314
+ cursor: not-allowed;
315
+ opacity: 0.45;
316
+ }
317
+
318
+ &:hover:not(:disabled) {
319
+ background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
320
+ }
214
321
  }
215
322
 
216
- .vm-data-i18n-btn__hint {
217
- margin: 0;
218
- font-size: 13px;
219
- color: var(--muted-foreground);
323
+ /* 复用 vm-upsert 栅格与控件视觉(与业务编辑弹窗一致) */
324
+ .vm-crud-upsert__body {
325
+ display: grid;
326
+ grid-template-columns: repeat(24, minmax(0, 1fr));
327
+ gap: 12px 16px;
328
+ padding: 8px 22px 16px;
220
329
 
221
- code {
222
- font-family: ui-monospace, monospace;
223
- color: var(--foreground);
330
+ @media (max-width: 768px) {
331
+ grid-template-columns: repeat(12, minmax(0, 1fr));
332
+ gap: 10px;
333
+ padding: 10px 12px;
224
334
  }
225
335
  }
226
336
 
227
- .vm-data-i18n-btn__field {
228
- display: flex;
229
- flex-direction: column;
230
- gap: 6px;
337
+ .vm-crud-upsert__label {
338
+ display: inline-flex;
339
+ align-items: center;
340
+ gap: 2px;
341
+ margin-bottom: 6px;
231
342
  font-size: 12px;
232
343
  color: var(--muted-foreground);
233
344
  }
345
+
346
+ .vm-crud-upsert__req {
347
+ color: var(--destructive);
348
+ }
349
+
350
+ .vm-crud-upsert__field {
351
+ min-width: 0;
352
+
353
+ :deep(.vm-crud-upsert__control),
354
+ :deep(.vm-crud-select.vm-crud-upsert__control) {
355
+ width: 100%;
356
+ height: var(--vm-control-height) !important;
357
+ min-height: var(--vm-control-height);
358
+ border-radius: var(--vm-control-radius) !important;
359
+ font-size: var(--vm-control-font-size) !important;
360
+ }
361
+
362
+ :deep(input.vm-crud-upsert__control),
363
+ :deep(.vm-crud-upsert__control[data-slot='input']) {
364
+ background: var(--card) !important;
365
+ }
366
+
367
+ :deep(.vm-crud-select.vm-crud-upsert__control) {
368
+ background: var(--muted) !important;
369
+ }
370
+ }
234
371
  </style>
@@ -0,0 +1,176 @@
1
+ <template>
2
+ <DropdownMenu v-if="langs.length" :modal="false">
3
+ <DropdownMenuTrigger as-child>
4
+ <button
5
+ type="button"
6
+ class="vm-crud-toolbar__btn is-ghost vm-data-i18n-lang__btn"
7
+ :disabled="switching"
8
+ :title="current?.name || langCode"
9
+ >
10
+ <span class="vm-data-i18n-lang__flag" aria-hidden="true">
11
+ {{ current?.flag || '🏳️' }}
12
+ </span>
13
+ 切换
14
+ </button>
15
+ </DropdownMenuTrigger>
16
+ <DropdownMenuContent align="start" class="vm-data-i18n-lang__menu">
17
+ <DropdownMenuItem
18
+ v-for="lang in langs"
19
+ :key="lang.value"
20
+ class="vm-data-i18n-lang__item"
21
+ :class="{ 'is-active': langCode === lang.value }"
22
+ :disabled="switching"
23
+ @click="switchLang(lang.value)"
24
+ >
25
+ <span class="vm-data-i18n-lang__flag" aria-hidden="true">
26
+ {{ lang.flag || '🏳️' }}
27
+ </span>
28
+ <span class="vm-data-i18n-lang__label">{{ lang.name || lang.label }}</span>
29
+ </DropdownMenuItem>
30
+ </DropdownMenuContent>
31
+ </DropdownMenu>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import { computed, onMounted, ref, watch } from 'vue'
36
+ import { useVome } from '../hooks/useVome'
37
+ import { useCrud } from './useCrud'
38
+ import {
39
+ DropdownMenu,
40
+ DropdownMenuContent,
41
+ DropdownMenuItem,
42
+ DropdownMenuTrigger,
43
+ } from '../components/ui/dropdown-menu'
44
+ import type { DataI18nLangOption } from './data-i18n'
45
+
46
+ defineOptions({ name: 'vm-data-i18n-lang' })
47
+
48
+ const crud = useCrud()
49
+ const { service } = useVome()
50
+ const langs = ref<DataI18nLangOption[]>([])
51
+ const switching = ref(false)
52
+
53
+ const langCode = computed({
54
+ get: () => crud.dataI18nLangCode?.value || '',
55
+ set: (v: string) => {
56
+ if (crud.dataI18nLangCode) crud.dataI18nLangCode.value = v
57
+ },
58
+ })
59
+
60
+ const current = computed(
61
+ () => langs.value.find((l) => l.value === langCode.value) || langs.value[0],
62
+ )
63
+
64
+ async function loadLangs() {
65
+ const langSvc = (service as { i18n?: { lang?: { list?: (q: unknown) => Promise<unknown> } } })
66
+ .i18n?.lang
67
+ if (!langSvc?.list) {
68
+ langs.value = [{ label: 'en-US', value: 'en-US', name: 'en-US', flag: '🇺🇸' }]
69
+ ensureLang()
70
+ return
71
+ }
72
+ try {
73
+ const rows = (await langSvc.list({ status: 1 })) as Array<{
74
+ code?: string
75
+ name?: string
76
+ flag?: string
77
+ }>
78
+ langs.value = (rows || [])
79
+ .filter((r) => r.code && r.code !== 'zh-CN')
80
+ .map((r) => ({
81
+ label: `${r.name || r.code} (${r.code})`,
82
+ value: String(r.code),
83
+ name: String(r.name || r.code),
84
+ flag: r.flag || '🏳️',
85
+ }))
86
+ } catch {
87
+ langs.value = [{ label: 'en-US', value: 'en-US', name: 'en-US', flag: '🇺🇸' }]
88
+ }
89
+ ensureLang()
90
+ if (crud.trashMode.value === 'dataI18n') {
91
+ await crud.refresh()
92
+ }
93
+ }
94
+
95
+ function ensureLang() {
96
+ if (!langs.value.length) return
97
+ if (!langCode.value || !langs.value.some((l) => l.value === langCode.value)) {
98
+ langCode.value = langs.value[0]!.value
99
+ }
100
+ }
101
+
102
+ async function switchLang(code: string) {
103
+ if (switching.value || !code || code === langCode.value) return
104
+ switching.value = true
105
+ try {
106
+ langCode.value = code
107
+ crud.pagination.value = { ...crud.pagination.value, page: 1 }
108
+ await crud.refresh()
109
+ } finally {
110
+ switching.value = false
111
+ }
112
+ }
113
+
114
+ watch(
115
+ () => crud.trashMode.value,
116
+ (mode) => {
117
+ if (mode === 'dataI18n') void loadLangs()
118
+ },
119
+ )
120
+
121
+ onMounted(() => {
122
+ if (crud.trashMode.value === 'dataI18n') void loadLangs()
123
+ })
124
+ </script>
125
+
126
+ <style lang="scss" scoped>
127
+ .vm-crud-toolbar__btn {
128
+ display: inline-flex;
129
+ height: 36px;
130
+ align-items: center;
131
+ gap: 4px;
132
+ padding: 0 14px;
133
+ border: none;
134
+ border-radius: 12px;
135
+ font-size: 13px;
136
+ font-weight: 600;
137
+ cursor: pointer;
138
+ background: var(--muted);
139
+ color: var(--foreground);
140
+ transition: background 0.15s ease, opacity 0.15s ease;
141
+
142
+ &:disabled {
143
+ cursor: not-allowed;
144
+ opacity: 0.45;
145
+ }
146
+
147
+ &:hover:not(:disabled) {
148
+ background: color-mix(in srgb, var(--muted) 70%, var(--foreground) 8%);
149
+ }
150
+ }
151
+
152
+ .vm-data-i18n-lang__flag {
153
+ font-size: 16px;
154
+ line-height: 1;
155
+ }
156
+
157
+ .vm-data-i18n-lang__menu {
158
+ min-width: 140px;
159
+ }
160
+
161
+ .vm-data-i18n-lang__item {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 8px;
165
+ cursor: pointer;
166
+
167
+ &.is-active {
168
+ background: color-mix(in srgb, var(--brand) 12%, transparent);
169
+ color: var(--brand);
170
+ }
171
+ }
172
+
173
+ .vm-data-i18n-lang__label {
174
+ font-size: 13px;
175
+ }
176
+ </style>