@vc-shell/vc-app-skill 2.0.0-alpha.23 → 2.0.0-alpha.25

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 (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/knowledge-stats.cjs +135 -0
  3. package/bin/sync-docs.cjs +62 -0
  4. package/package.json +5 -1
  5. package/runtime/VERSION +1 -1
  6. package/runtime/agents/details-blade-generator.md +75 -14
  7. package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
  8. package/runtime/knowledge/docs/core/api/platform.docs.md +14 -7
  9. package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +6 -0
  10. package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +6 -0
  11. package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +149 -0
  12. package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +7 -0
  13. package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +7 -0
  14. package/runtime/knowledge/docs/core/composables/useBladeWidgets.docs.md +164 -9
  15. package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +6 -0
  16. package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +6 -0
  17. package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +6 -0
  18. package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +6 -0
  19. package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +2 -2
  20. package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +6 -0
  21. package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +6 -0
  22. package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +6 -0
  23. package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +74 -0
  24. package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +6 -0
  25. package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +6 -0
  26. package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +6 -0
  27. package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +6 -0
  28. package/runtime/knowledge/docs/injection-keys.docs.md +1 -1
  29. package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +29 -33
  30. package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +3 -3
  31. package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +11 -0
  32. package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +11 -0
  33. package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +11 -0
  34. package/runtime/knowledge/docs/ui/components/organisms/vc-table/vc-data-table.docs.md +12 -0
  35. package/runtime/knowledge/index.md +60 -0
  36. package/runtime/knowledge/patterns/assets-management.md +213 -0
  37. package/runtime/knowledge/patterns/child-blade-flow.md +277 -0
  38. package/runtime/knowledge/patterns/details-blade-pattern.md +350 -3
  39. package/runtime/knowledge/patterns/extension-points-usage.md +308 -0
  40. package/runtime/knowledge/patterns/form-validation.md +377 -0
  41. package/runtime/knowledge/patterns/multilanguage-fields.md +239 -0
  42. package/runtime/knowledge/patterns/signalr-notifications.md +237 -0
  43. package/runtime/vc-app.md +44 -5
  44. package/runtime/knowledge/docs/core/composables/useWidget/useWidget.docs.md +0 -159
  45. package/runtime/knowledge/docs/shell/components/app-switcher/app-switcher.docs.md +0 -104
@@ -0,0 +1,377 @@
1
+ # Form Validation Pattern
2
+
3
+ Reference source: `apps/vendor-portal/src/modules/products/components/ProductDetailsBase.vue`
4
+ Secondary source: `apps/vendor-portal/src/modules/offers/pages/offers-details.vue`
5
+
6
+ ## Overview
7
+
8
+ Form validation in vc-shell uses vee-validate v4 with a declarative `<Field>` wrapper around each input. The framework auto-registers all standard rules plus custom vc-shell rules at startup -- no per-component imports needed. `useForm()` provides reactive form-level validity metadata for controlling toolbar buttons and save guards.
9
+
10
+ ---
11
+
12
+ ## Core Wiring Pattern: Field + VcInput
13
+
14
+ Every validated field follows the same three-part contract:
15
+
16
+ 1. `<Field>` tracks the value and applies rules
17
+ 2. The inner component uses `v-model` for two-way binding
18
+ 3. `handleChange` keeps vee-validate in sync on every update
19
+
20
+ ```vue
21
+ <Field
22
+ v-slot="{ errorMessage, handleChange, errors }"
23
+ :model-value="item.name"
24
+ name="name"
25
+ rules="required"
26
+ >
27
+ <VcInput
28
+ v-model="item.name"
29
+ label="Name"
30
+ required
31
+ :error="!!errors.length"
32
+ :error-message="errorMessage"
33
+ @update:model-value="handleChange"
34
+ />
35
+ </Field>
36
+ ```
37
+
38
+ **Critical:** Without `@update:model-value="handleChange"`, vee-validate never learns the value changed and validation will not trigger. Without `:model-value` on `<Field>`, the rule has nothing to validate against.
39
+
40
+ ---
41
+
42
+ ## Wiring Other Input Components
43
+
44
+ The same pattern applies to every vc-shell input. Only the inner component changes.
45
+
46
+ ### VcSelect
47
+
48
+ ```vue
49
+ <Field
50
+ v-slot="{ errorMessage, handleChange, errors }"
51
+ :model-value="item.categoryId"
52
+ name="categoryId"
53
+ rules="required"
54
+ >
55
+ <VcSelect
56
+ v-model="item.categoryId"
57
+ label="Category"
58
+ :options="categories"
59
+ option-value="id"
60
+ option-label="name"
61
+ required
62
+ :clearable="false"
63
+ :error="!!errors.length"
64
+ :error-message="errorMessage"
65
+ @update:model-value="handleChange"
66
+ />
67
+ </Field>
68
+ ```
69
+
70
+ ### VcEditor (Rich Text)
71
+
72
+ ```vue
73
+ <Field
74
+ v-slot="{ errorMessage, handleChange, errors }"
75
+ :model-value="item.description"
76
+ name="description"
77
+ rules="required"
78
+ >
79
+ <VcEditor
80
+ v-model="item.description"
81
+ label="Description"
82
+ required
83
+ :error="!!errors.length"
84
+ :error-message="errorMessage"
85
+ @update:model-value="handleChange"
86
+ />
87
+ </Field>
88
+ ```
89
+
90
+ ### VcTextarea
91
+
92
+ ```vue
93
+ <Field
94
+ v-slot="{ errorMessage, handleChange, errors }"
95
+ :model-value="item.notes"
96
+ name="notes"
97
+ rules="required|min:10"
98
+ >
99
+ <VcTextarea
100
+ v-model="item.notes"
101
+ label="Notes"
102
+ required
103
+ :error="!!errors.length"
104
+ :error-message="errorMessage"
105
+ @update:model-value="handleChange"
106
+ />
107
+ </Field>
108
+ ```
109
+
110
+ ### Fields That Do Not Need Validation
111
+
112
+ Simple toggles and checkboxes typically skip the `<Field>` wrapper:
113
+
114
+ ```vue
115
+ <VcSwitch v-model="item.isActive" label="Active" />
116
+ <VcCheckbox v-model="item.acceptTerms" label="I accept the terms" />
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Available Validation Rules
122
+
123
+ Rules are composed with `|` (pipe) in the `rules` string.
124
+
125
+ ### Standard Rules (from @vee-validate/rules)
126
+
127
+ | Rule | Example | Description |
128
+ |------|---------|-------------|
129
+ | `required` | `"required"` | Must have a value |
130
+ | `email` | `"required\|email"` | Valid email format |
131
+ | `min:N` | `"required\|min:3"` | Minimum string length |
132
+ | `max:N` | `"max:255"` | Maximum string length |
133
+ | `min_value:N` | `"min_value:0"` | Minimum numeric value |
134
+ | `between:N,M` | `"between:1,100"` | Numeric range |
135
+ | `numeric` | `"numeric"` | Digits only |
136
+ | `alpha_dash` | `"alpha_dash"` | Letters, digits, dashes, underscores |
137
+ | `regex:P` | `"regex:^[A-Z]+"` | Custom regex match |
138
+ | `confirmed:F` | `"confirmed:password"` | Must match another field |
139
+ | `url` | `"url"` | Valid URL format |
140
+
141
+ ### Custom vc-shell Rules
142
+
143
+ | Rule | Params | Description |
144
+ |------|--------|-------------|
145
+ | `bigint` | -- | Value is a safe integer (`Number.isSafeInteger`) |
146
+ | `mindimensions` | `[width, height]` | Image meets minimum pixel dimensions |
147
+ | `fileWeight` | `[sizeInKB]` | File size under limit (KB) |
148
+ | `before` | `[targetDate]` | Date is before target |
149
+ | `after` | `[targetDate]` | Date is after target |
150
+
151
+ ---
152
+
153
+ ## Form-Level Validation with useForm
154
+
155
+ `useForm()` provides reactive metadata about all `<Field>` components in the current component tree.
156
+
157
+ ```ts
158
+ import { useForm, Field } from "vee-validate";
159
+
160
+ const { meta, setFieldError } = useForm({
161
+ validateOnMount: false, // Always false -- avoids errors on blade open
162
+ });
163
+
164
+ // meta.value.valid -- true when ALL fields pass
165
+ // meta.value.dirty -- true when ANY field has changed
166
+ ```
167
+
168
+ ### Disabling the Save Button
169
+
170
+ Wire `meta.value.valid` into the toolbar's `disabled` computed:
171
+
172
+ ```ts
173
+ const bladeToolbar = ref<IBladeToolbar[]>([
174
+ {
175
+ id: "save",
176
+ title: "Save",
177
+ icon: "lucide-save",
178
+ disabled: computed(() => !meta.value.valid || !modified.value),
179
+ async clickHandler() {
180
+ if (meta.value.valid) {
181
+ await saveItem();
182
+ callParent("reload");
183
+ closeSelf();
184
+ }
185
+ },
186
+ },
187
+ ]);
188
+ ```
189
+
190
+ **Tip:** Use `modified.value` from your details composable instead of `meta.value.dirty`. The `dirty` flag only tracks vee-validate Field changes and misses switches, checkboxes, and other non-validated inputs.
191
+
192
+ ---
193
+
194
+ ## Dynamic and Conditional Rules
195
+
196
+ When rules depend on reactive state, use a computed or `:rules` binding:
197
+
198
+ ```vue
199
+ <!-- Conditionally required -->
200
+ <Field
201
+ :rules="item.trackInventory ? 'required|bigint|min_value:0' : 'bigint|min_value:0'"
202
+ :model-value="item.quantity"
203
+ name="quantity"
204
+ v-slot="{ errorMessage, handleChange, errors }"
205
+ >
206
+ <VcInput
207
+ v-model="item.quantity"
208
+ type="number"
209
+ label="Quantity"
210
+ :required="item.trackInventory"
211
+ :error="!!errors.length"
212
+ :error-message="errorMessage"
213
+ @update:model-value="handleChange"
214
+ />
215
+ </Field>
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Cross-Field Validation
221
+
222
+ Use the `after` / `before` custom rules with reactive parameters, or the `confirmed` standard rule.
223
+
224
+ ### Date Range (start must be before end)
225
+
226
+ ```vue
227
+ <script setup lang="ts">
228
+ import { ref, computed } from "vue";
229
+ import { Field, useForm } from "vee-validate";
230
+
231
+ const { meta } = useForm({ validateOnMount: false });
232
+
233
+ const startDate = ref("");
234
+ const endDate = ref("");
235
+ const endDateRules = computed(() => `required|after:${startDate.value}`);
236
+ </script>
237
+
238
+ <template>
239
+ <VcForm>
240
+ <Field
241
+ v-slot="{ errorMessage, handleChange, errors }"
242
+ :model-value="startDate"
243
+ name="startDate"
244
+ rules="required"
245
+ >
246
+ <VcInput
247
+ v-model="startDate"
248
+ type="date"
249
+ label="Start Date"
250
+ required
251
+ :error="!!errors.length"
252
+ :error-message="errorMessage"
253
+ @update:model-value="handleChange"
254
+ />
255
+ </Field>
256
+
257
+ <Field
258
+ v-slot="{ errorMessage, handleChange, errors }"
259
+ :model-value="endDate"
260
+ name="endDate"
261
+ :rules="endDateRules"
262
+ >
263
+ <VcInput
264
+ v-model="endDate"
265
+ type="date"
266
+ label="End Date"
267
+ required
268
+ :error="!!errors.length"
269
+ :error-message="errorMessage"
270
+ @update:model-value="handleChange"
271
+ />
272
+ </Field>
273
+ </VcForm>
274
+ </template>
275
+ ```
276
+
277
+ ### Password Confirmation
278
+
279
+ ```vue
280
+ <Field
281
+ v-slot="{ errorMessage, handleChange, errors }"
282
+ :model-value="password"
283
+ name="password"
284
+ rules="required|min:8"
285
+ >
286
+ <VcInput
287
+ v-model="password"
288
+ type="password"
289
+ label="Password"
290
+ required
291
+ :error="!!errors.length"
292
+ :error-message="errorMessage"
293
+ @update:model-value="handleChange"
294
+ />
295
+ </Field>
296
+
297
+ <Field
298
+ v-slot="{ errorMessage, handleChange, errors }"
299
+ :model-value="confirmPassword"
300
+ name="confirmPassword"
301
+ rules="required|confirmed:password"
302
+ >
303
+ <VcInput
304
+ v-model="confirmPassword"
305
+ type="password"
306
+ label="Confirm Password"
307
+ required
308
+ :error="!!errors.length"
309
+ :error-message="errorMessage"
310
+ @update:model-value="handleChange"
311
+ />
312
+ </Field>
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Server-Side Validation Errors
318
+
319
+ Use `setFieldError()` to display API-returned errors on specific fields:
320
+
321
+ ```ts
322
+ const { setFieldError } = useForm({ validateOnMount: false });
323
+
324
+ async function save() {
325
+ try {
326
+ await api.updateItem(item.value);
327
+ } catch (e: unknown) {
328
+ if (e instanceof ApiValidationError) {
329
+ for (const [field, messages] of Object.entries(e.errors)) {
330
+ setFieldError(field, messages.join("\n"));
331
+ }
332
+ }
333
+ }
334
+ }
335
+ ```
336
+
337
+ ---
338
+
339
+ ## Validation Schema (Alternative to Inline Rules)
340
+
341
+ For forms with many fields, define all rules in one object:
342
+
343
+ ```ts
344
+ const { handleSubmit } = useForm({
345
+ validateOnMount: false,
346
+ validationSchema: {
347
+ name: "required|min:3",
348
+ email: "required|email",
349
+ sku: "required|alpha_dash|min:3|max:64",
350
+ price: "required|numeric|bigint",
351
+ startDate: "required",
352
+ },
353
+ });
354
+ ```
355
+
356
+ When using a schema, `<Field>` components still need `name` but can omit `rules` -- the schema supplies them.
357
+
358
+ ---
359
+
360
+ ## Common Mistakes
361
+
362
+ 1. **Missing `handleChange`** -- vee-validate stays stale; validation never fires on input.
363
+ 2. **Missing `:model-value` on Field** -- rule has no value to validate; always shows as valid.
364
+ 3. **`validateOnMount: true`** -- shows errors on every field the moment the blade opens. Always use `false`.
365
+ 4. **Using `meta.value.dirty` for save button** -- misses non-validated fields (switches, checkboxes). Use a dedicated `modified` ref from your composable.
366
+ 5. **Static rule string for cross-field** -- `rules="after:${startDate.value}"` captures the value once at template compile. Use `:rules="endDateRules"` with a computed.
367
+
368
+ ---
369
+
370
+ ## Key Rules
371
+
372
+ 1. Always set `validateOnMount: false` in `useForm()`.
373
+ 2. Every validated field must wire all three: `:model-value`, `v-model`, and `@update:model-value="handleChange"`.
374
+ 3. Use pipe-delimited rule strings (`"required|email|min:3"`) for inline rules.
375
+ 4. Use `:rules` binding with computed for dynamic/cross-field rules.
376
+ 5. Disable save via `computed(() => !meta.value.valid || !modified.value)` on the toolbar item.
377
+ 6. Fields without validation rules (switches, checkboxes) do not need a `<Field>` wrapper.
@@ -0,0 +1,239 @@
1
+ # Multilanguage Fields Pattern
2
+
3
+ Add multilanguage content editing to a detail blade. This is for switching which language version of entity data the user edits — distinct from the UI locale (app translation language).
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ - Entity has multilingual fields stored as `Record<string, string>` (keyed by locale code like `"en-US"`, `"de-DE"`)
10
+ - Module registers its own locale files via `defineAppModule({ locales })`
11
+
12
+ ---
13
+
14
+ ## Step 1 — Load Available Languages
15
+
16
+ Create or reuse a composable that fetches content languages from the API and exposes a reactive `currentLocale`, options list, and flag URLs.
17
+
18
+ ```ts
19
+ import { ref, computed, watchEffect, onBeforeUnmount, Ref } from "vue";
20
+ import { useLanguages } from "@vc-shell/framework";
21
+
22
+ const currentLocale = ref("en-US");
23
+ const languages = ref<string[]>([]);
24
+ const localesOptions = ref<{ value: string; label: string }[]>([]);
25
+
26
+ export function useMultilanguage() {
27
+ const { getLocaleByTag, getFlag } = useLanguages();
28
+
29
+ const languageOptionsWithFlags = ref<{ value: string; label: string; flag?: string }[]>([]);
30
+ const isMultilanguage = computed(() => localesOptions.value.length > 1);
31
+
32
+ async function getLanguages() {
33
+ // Fetch from your API client
34
+ languages.value = await fetchAvailableLanguages();
35
+ localesOptions.value = languages.value.map((x) => ({
36
+ label: getLocaleByTag(x) || x,
37
+ value: x,
38
+ }));
39
+ }
40
+
41
+ // Resolve flags in parallel
42
+ watchEffect(async () => {
43
+ if (!isMultilanguage.value) return;
44
+ languageOptionsWithFlags.value = await Promise.all(
45
+ localesOptions.value.map(async (lang) => ({
46
+ ...lang,
47
+ flag: await getFlag(lang.value),
48
+ })),
49
+ );
50
+ });
51
+
52
+ const setLocale = (locale: string) => {
53
+ currentLocale.value = locale;
54
+ };
55
+
56
+ // Reset on unmount so next blade starts fresh
57
+ onBeforeUnmount(() => {
58
+ currentLocale.value = "en-US";
59
+ });
60
+
61
+ return { currentLocale, localesOptions, languageOptionsWithFlags, isMultilanguage, setLocale, getLanguages };
62
+ }
63
+ ```
64
+
65
+ Key points:
66
+ - `currentLocale` and `languages` are module-level refs (shared across blades in the same module).
67
+ - Deduplicate concurrent `getLanguages` calls with a promise guard if multiple blades mount simultaneously.
68
+ - `useLanguages()` from the framework provides `getLocaleByTag` (display name) and `getFlag` (async flag URL).
69
+
70
+ ---
71
+
72
+ ## Step 2 — Place MultilanguageSelector in the Blade
73
+
74
+ Use the blade's `#actions` slot to position the language picker in the toolbar area.
75
+
76
+ ```vue
77
+ <template>
78
+ <VcBlade :title="bladeTitle" :toolbar-items="bladeToolbar">
79
+ <template #actions>
80
+ <MultilanguageSelector
81
+ v-if="isMultilanguage"
82
+ v-model="currentLocale"
83
+ :options="languageOptionsWithFlags"
84
+ />
85
+ </template>
86
+
87
+ <!-- form fields below -->
88
+ </VcBlade>
89
+ </template>
90
+ ```
91
+
92
+ Import `MultilanguageSelector` from `@vc-shell/framework`. It renders as a compact circular flag button that opens a dropdown. Only show it when `isMultilanguage` is true (more than one language available).
93
+
94
+ ---
95
+
96
+ ## Step 3 — Create Localized Computed Properties
97
+
98
+ For each multilingual field on your entity, create a writable computed that reads/writes the value for `currentLocale`.
99
+
100
+ ```ts
101
+ const { currentLocale } = useMultilanguage();
102
+
103
+ // Assuming item.value is the entity with multilingual fields
104
+ const localizedName = computed({
105
+ get: () => item.value?.names?.[currentLocale.value] ?? "",
106
+ set: (val: string) => {
107
+ if (item.value) {
108
+ if (!item.value.names) item.value.names = {};
109
+ item.value.names[currentLocale.value] = val;
110
+ }
111
+ },
112
+ });
113
+
114
+ const localizedDescription = computed({
115
+ get: () => item.value?.descriptions?.[currentLocale.value] ?? "",
116
+ set: (val: string) => {
117
+ if (item.value) {
118
+ if (!item.value.descriptions) item.value.descriptions = {};
119
+ item.value.descriptions[currentLocale.value] = val;
120
+ }
121
+ },
122
+ });
123
+ ```
124
+
125
+ Guard against undefined with `??` — when a language has no content yet, the field shows empty.
126
+
127
+ ---
128
+
129
+ ## Step 4 — Wire Form Fields with Multilanguage Props
130
+
131
+ `VcInput` and `VcEditor` both accept `:multilanguage` and `:current-language` props. These add a visual language indicator on the field label.
132
+
133
+ ```vue
134
+ <VcInput
135
+ v-model="localizedName"
136
+ :label="t('MY_MODULE.FIELDS.NAME')"
137
+ multilanguage
138
+ :current-language="currentLocale"
139
+ />
140
+
141
+ <VcEditor
142
+ v-model="localizedDescription"
143
+ :label="t('MY_MODULE.FIELDS.DESCRIPTION')"
144
+ multilanguage
145
+ :current-language="currentLocale"
146
+ />
147
+ ```
148
+
149
+ - `multilanguage` (boolean) — enables the language indicator badge on the label.
150
+ - `current-language` (string) — the locale code shown in the indicator (e.g., `"de-DE"`).
151
+ - The actual value switching is handled by your localized computed, not by the component itself.
152
+
153
+ ---
154
+
155
+ ## Step 5 — Module Locale Files
156
+
157
+ Register translation files in your module definition so the framework merges them into the global i18n instance.
158
+
159
+ ```
160
+ modules/my-module/
161
+ locales/
162
+ en.json
163
+ de.json
164
+ index.ts
165
+ ```
166
+
167
+ ```ts
168
+ // modules/my-module/index.ts
169
+ import * as en from "./locales/en.json";
170
+ import * as de from "./locales/de.json";
171
+
172
+ export default defineAppModule({
173
+ locales: { en, de },
174
+ // ...other module config
175
+ });
176
+ ```
177
+
178
+ Locale files use nested JSON with a module namespace:
179
+
180
+ ```json
181
+ {
182
+ "MY_MODULE": {
183
+ "FIELDS": {
184
+ "NAME": "Name",
185
+ "DESCRIPTION": "Description"
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
191
+ The framework calls `i18n.global.mergeLocaleMessage()` during module installation — no manual merge needed.
192
+
193
+ ---
194
+
195
+ ## Complete Blade Example
196
+
197
+ ```vue
198
+ <script setup lang="ts">
199
+ import { computed } from "vue";
200
+ import { useI18n } from "vue-i18n";
201
+ import { MultilanguageSelector } from "@vc-shell/framework";
202
+ import { useMultilanguage } from "../composables/useMultilanguage";
203
+
204
+ const { t } = useI18n();
205
+ const { currentLocale, languageOptionsWithFlags, isMultilanguage, getLanguages } = useMultilanguage();
206
+
207
+ const props = defineProps<{ param?: { item: Record<string, any> } }>();
208
+ const item = computed(() => props.param?.item);
209
+
210
+ // Call once on blade open
211
+ getLanguages();
212
+
213
+ const localizedName = computed({
214
+ get: () => item.value?.names?.[currentLocale.value] ?? "",
215
+ set: (val: string) => {
216
+ if (item.value) item.value.names[currentLocale.value] = val;
217
+ },
218
+ });
219
+ </script>
220
+
221
+ <template>
222
+ <VcBlade :title="t('MY_MODULE.BLADE_TITLE')">
223
+ <template #actions>
224
+ <MultilanguageSelector
225
+ v-if="isMultilanguage"
226
+ v-model="currentLocale"
227
+ :options="languageOptionsWithFlags"
228
+ />
229
+ </template>
230
+
231
+ <VcInput
232
+ v-model="localizedName"
233
+ :label="t('MY_MODULE.FIELDS.NAME')"
234
+ multilanguage
235
+ :current-language="currentLocale"
236
+ />
237
+ </VcBlade>
238
+ </template>
239
+ ```