Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions web/__test__/helpers/jsonforms-i18n.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { defineComponent, h, nextTick, ref } from 'vue';
import { createI18n } from 'vue-i18n';

import { jsonFormsAjv } from '@unraid/ui';
import { JsonForms } from '@jsonforms/vue';
import { vanillaRenderers } from '@jsonforms/vue-vanilla';
import { render, screen } from '@testing-library/vue';
import { useJsonFormsI18n } from '~/helpers/jsonforms-i18n';
import { describe, expect, it } from 'vitest';

const schema = {
type: 'object',
properties: {
provider: {
type: 'string',
minLength: 1,
i18n: 'jsonforms.test.provider',
},
},
required: ['provider'],
} as const;

const uischema = {
type: 'Control',
scope: '#/properties/provider',
} as const;
Comment on lines +11 to +26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Schema description not rendering in tests.

The test expects to find "English description" (line 101), but static analysis shows it's not rendered. The vanilla renderers may not display schema descriptions by default. Consider adding uischema options to enable description display:

 const uischema = {
   type: 'Control',
   scope: '#/properties/provider',
+  options: {
+    showDescription: true,
+  },
 } as const;

Alternatively, verify that the vanilla renderers support description rendering and whether additional configuration is needed.

🤖 Prompt for AI Agents
In web/__test__/helpers/jsonforms-i18n.test.ts around lines 11 to 26, the test
expects the string "English description" but the current schema and uischema
don't render schema descriptions by default; update the provider property's
schema to include a description field with the expected text and add uischema
options to enable description rendering (e.g., add an options object that turns
on description display supported by your vanilla renderers), or alternatively
adjust the test to use a renderer/config that shows schema descriptions if your
current renderers cannot; ensure the final setup actually renders the
description string the test asserts.


const messages = {
en: {
jsonforms: {
test: {
provider: {
label: 'Provider label (en)',
description: 'English description',
},
},
errors: {
required: 'Field is required (en)',
},
},
},
fr: {
jsonforms: {
test: {
provider: {
label: 'Libellé du fournisseur (fr)',
description: 'Description française',
},
},
errors: {
required: 'Champ requis (fr)',
},
},
},
} as const;

const TestHarness = defineComponent(() => {
const formData = ref<Record<string, unknown>>({});
const i18nState = useJsonFormsI18n();

const handleChange = ({ data }: { data: Record<string, unknown> }) => {
formData.value = data;
};

return () =>
h(JsonForms, {
schema,
uischema,
data: formData.value,
renderers: vanillaRenderers,
ajv: jsonFormsAjv,
i18n: i18nState.value,
validationMode: 'ValidateAndShow',
onChange: handleChange,
});
});

const renderJsonForms = async (locale: 'en' | 'fr' = 'en') => {
const i18n = createI18n({
legacy: false,
locale,
messages,
});

const utils = render(TestHarness, {
global: {
plugins: [i18n],
},
});

await screen.findByText('Provider label (en)');

Check failure on line 91 in web/__test__/helpers/jsonforms-i18n.test.ts

View workflow job for this annotation

GitHub Actions / Test API

__test__/helpers/jsonforms-i18n.test.ts > useJsonFormsI18n > responds to updated translations for the active locale

TestingLibraryElementError: Found multiple elements with the text: Provider label (en) Here are the matching elements: Ignored nodes: comments, script, style <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> Ignored nodes: comments, script, style <label class="label required" for="#/properties/provider2-input" > Provider label (en) <span class="asterisk" > * </span> </label> (If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)). Ignored nodes: comments, script, style <body> <div> <div class="control" id="#/properties/provider" > <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> <div> <div class="control" id="#/properties/provider2" > <label class="label required" for="#/properties/provider2-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider2-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> </body> Ignored nodes: comments, script, style <body> <div> <div class="control" id="#/properties/provider" > <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> <div> <div class="control" id="#/properties/provider2" > <label class="label required" for="#/properties/provider2-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider2-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> </body> ❯ waitForWrapper ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/wait-for.js:163:27 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:86:33 ❯ renderJsonForms __test__/helpers/jsonforms-i18n.test.ts:91:16 ❯ __test__/helpers/jsonforms-i18n.test.ts:113:28

Check failure on line 91 in web/__test__/helpers/jsonforms-i18n.test.ts

View workflow job for this annotation

GitHub Actions / Test API

__test__/helpers/jsonforms-i18n.test.ts > useJsonFormsI18n > responds to updated translations for the active locale

TestingLibraryElementError: Found multiple elements with the text: Provider label (en) Here are the matching elements: Ignored nodes: comments, script, style <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> Ignored nodes: comments, script, style <label class="label required" for="#/properties/provider2-input" > Provider label (en) <span class="asterisk" > * </span> </label> (If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)). Ignored nodes: comments, script, style <body> <div> <div class="control" id="#/properties/provider" > <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> <div> <div class="control" id="#/properties/provider2" > <label class="label required" for="#/properties/provider2-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider2-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> </body> Ignored nodes: comments, script, style <body> <div> <div class="control" id="#/properties/provider" > <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> <div> <div class="control" id="#/properties/provider2" > <label class="label required" for="#/properties/provider2-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider2-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> </body> ❯ waitForWrapper ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/wait-for.js:163:27 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:86:33 ❯ renderJsonForms __test__/helpers/jsonforms-i18n.test.ts:91:16 ❯ __test__/helpers/jsonforms-i18n.test.ts:113:28

return { i18n, ...utils };
};
Comment on lines +78 to +94
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add cleanup between tests to prevent duplicate DOM elements.

Static analysis shows "Found multiple elements with the text: Provider label (en)" at line 91, indicating previous test renders aren't being cleaned up. Add cleanup to prevent test pollution:

-import { describe, expect, it } from 'vitest';
+import { afterEach, describe, expect, it } from 'vitest';
+import { cleanup } from '@testing-library/vue';

 // ... schemas and component ...

 describe('useJsonFormsI18n', () => {
+  afterEach(() => {
+    cleanup();
+  });
+
   it('translates labels, descriptions, and errors when the locale changes', async () => {

As per coding guidelines.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Check: Test API

[failure] 91-91: test/helpers/jsonforms-i18n.test.ts > useJsonFormsI18n > responds to updated translations for the active locale
TestingLibraryElementError: Found multiple elements with the text: Provider label (en)

Here are the matching elements:

Ignored nodes: comments, script, style
<label
class="label required"
for="#/properties/provider-input"

Provider label (en)
<span
class="asterisk"

*

Ignored nodes: comments, script, style
<label
class="label required"
for="#/properties/provider2-input"

Provider label (en)
<span
class="asterisk"

*

(If this is intentional, then use the *AllBy* variant of the query (like queryAllByText, getAllByText, or findAllByText)).

Ignored nodes: comments, script, style

Provider label (en) *
    <input
      class="input"
      id="#/properties/provider-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>
Provider label (en) *
    <input
      class="input"
      id="#/properties/provider2-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>

Ignored nodes: comments, script, style

Provider label (en) *
    <input
      class="input"
      id="#/properties/provider-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>
Provider label (en) *
    <input
      class="input"
      id="#/properties/provider2-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>
❯ waitForWrapper ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/wait-for.js:163:27 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:86:33 ❯ renderJsonForms __test__/helpers/jsonforms-i18n.test.ts:91:16 ❯ __test__/helpers/jsonforms-i18n.test.ts:113:28

[failure] 91-91: test/helpers/jsonforms-i18n.test.ts > useJsonFormsI18n > responds to updated translations for the active locale
TestingLibraryElementError: Found multiple elements with the text: Provider label (en)

Here are the matching elements:

Ignored nodes: comments, script, style
<label
class="label required"
for="#/properties/provider-input"

Provider label (en)
<span
class="asterisk"

*

Ignored nodes: comments, script, style
<label
class="label required"
for="#/properties/provider2-input"

Provider label (en)
<span
class="asterisk"

*

(If this is intentional, then use the *AllBy* variant of the query (like queryAllByText, getAllByText, or findAllByText)).

Ignored nodes: comments, script, style

Provider label (en) *
    <input
      class="input"
      id="#/properties/provider-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>
Provider label (en) *
    <input
      class="input"
      id="#/properties/provider2-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>

Ignored nodes: comments, script, style

Provider label (en) *
    <input
      class="input"
      id="#/properties/provider-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>
Provider label (en) *
    <input
      class="input"
      id="#/properties/provider2-input"
    />
    
  </div>
  <div
    class="error"
  >
    jsonforms.test.provider.error.custom
  </div>
</div>
❯ waitForWrapper ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/wait-for.js:163:27 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:86:33 ❯ renderJsonForms __test__/helpers/jsonforms-i18n.test.ts:91:16 ❯ __test__/helpers/jsonforms-i18n.test.ts:113:28
🤖 Prompt for AI Agents
In web/__test__/helpers/jsonforms-i18n.test.ts around lines 78 to 94, tests are
leaving rendered DOM between runs causing "Found multiple elements with the
text: Provider label (en)"; add test cleanup to unmount/remove previous renders
by importing the testing-library cleanup helper and invoking it after each test
(e.g., add an afterEach(cleanup) hook) or call cleanup() in a beforeEach to
ensure a fresh DOM before renderJsonForms is used.


describe('useJsonFormsI18n', () => {
it('translates labels, descriptions, and errors when the locale changes', async () => {
const { i18n } = await renderJsonForms('en');

expect(await screen.findByText('Provider label (en)')).toBeTruthy();
expect(screen.getByText('English description')).toBeTruthy();

Check failure on line 101 in web/__test__/helpers/jsonforms-i18n.test.ts

View workflow job for this annotation

GitHub Actions / Test API

__test__/helpers/jsonforms-i18n.test.ts > useJsonFormsI18n > translates labels, descriptions, and errors when the locale changes

TestingLibraryElementError: Unable to find an element with the text: English description. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div class="control" id="#/properties/provider" > <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> </body> ❯ Object.getElementError ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/config.js:37:19 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __test__/helpers/jsonforms-i18n.test.ts:101:19

Check failure on line 101 in web/__test__/helpers/jsonforms-i18n.test.ts

View workflow job for this annotation

GitHub Actions / Test API

__test__/helpers/jsonforms-i18n.test.ts > useJsonFormsI18n > translates labels, descriptions, and errors when the locale changes

TestingLibraryElementError: Unable to find an element with the text: English description. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div class="control" id="#/properties/provider" > <label class="label required" for="#/properties/provider-input" > Provider label (en) <span class="asterisk" > * </span> </label> <div class="wrapper" > <input class="input" id="#/properties/provider-input" /> </div> <div class="error" > jsonforms.test.provider.error.custom </div> </div> </div> </body> ❯ Object.getElementError ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/config.js:37:19 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ ../node_modules/.pnpm/@testing-library+dom@9.3.4/node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ __test__/helpers/jsonforms-i18n.test.ts:101:19
expect(screen.getByText('Field is required (en)')).toBeTruthy();

i18n.global.locale.value = 'fr';
await nextTick();
await screen.findByText('Libellé du fournisseur (fr)');

expect(screen.getByText('Description française')).toBeTruthy();
expect(screen.getByText('Champ requis (fr)')).toBeTruthy();
});

it('responds to updated translations for the active locale', async () => {
const { i18n } = await renderJsonForms('en');

i18n.global.mergeLocaleMessage('en', {
jsonforms: {
test: {
provider: {
label: 'Provider label (updated)',
description: 'Updated English description',
},
},
},
errors: {
required: 'Field is required (updated)',
},
Comment on lines +115 to +126

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use correct path when updating error translation messages

The new i18n test updates error strings via mergeLocaleMessage under a top‑level errors key, but translateError looks for messages under jsonforms.errors.*. As written, the merged value is never read, so the assertion that 'Field is required (updated)' appears will always time out and the spec cannot pass. Move the errors object inside jsonforms (mirroring the initial message structure) or adjust the code under test to read from the root key.

Useful? React with 👍 / 👎.

});

await nextTick();
await screen.findByText('Provider label (updated)');

expect(screen.getByText('Updated English description')).toBeTruthy();
expect(screen.getByText('Field is required (updated)')).toBeTruthy();
});
Comment on lines +112 to +134
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix incorrect translation key for error messages.

Line 124-126 updates errors.required, but the translateError function in the helper (line 33 of jsonforms-i18n.ts) constructs keys as jsonforms.errors.{keyword}. The error translation will not work as expected.

Apply this diff:

     i18n.global.mergeLocaleMessage('en', {
       jsonforms: {
         test: {
           provider: {
             label: 'Provider label (updated)',
             description: 'Updated English description',
           },
         },
+        errors: {
+          required: 'Field is required (updated)',
+        },
       },
-      errors: {
-        required: 'Field is required (updated)',
-      },
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('responds to updated translations for the active locale', async () => {
const { i18n } = await renderJsonForms('en');
i18n.global.mergeLocaleMessage('en', {
jsonforms: {
test: {
provider: {
label: 'Provider label (updated)',
description: 'Updated English description',
},
},
},
errors: {
required: 'Field is required (updated)',
},
});
await nextTick();
await screen.findByText('Provider label (updated)');
expect(screen.getByText('Updated English description')).toBeTruthy();
expect(screen.getByText('Field is required (updated)')).toBeTruthy();
});
it('responds to updated translations for the active locale', async () => {
const { i18n } = await renderJsonForms('en');
i18n.global.mergeLocaleMessage('en', {
jsonforms: {
test: {
provider: {
label: 'Provider label (updated)',
description: 'Updated English description',
},
},
errors: {
required: 'Field is required (updated)',
},
},
});
await nextTick();
await screen.findByText('Provider label (updated)');
expect(screen.getByText('Updated English description')).toBeTruthy();
expect(screen.getByText('Field is required (updated)')).toBeTruthy();
});
🤖 Prompt for AI Agents
In web/__test__/helpers/jsonforms-i18n.test.ts around lines 112 to 134, the test
merges error translations under the top-level key "errors.required" but the
helper's translateError builds keys as "jsonforms.errors.{keyword}", so the
error translation isn't found; update the merged locale messages to place the
error translation under jsonforms.errors.required (i.e., nest the errors object
inside the jsonforms object as jsonforms: { errors: { required: 'Field is
required (updated)' } }) so the translateError lookup matches and the test
asserts the updated error text correctly.

});
18 changes: 15 additions & 3 deletions web/src/components/ApiKey/ApiKeyCreate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ const formData = ref<FormData>({
const formValid = ref(false);
const jsonFormsI18n = useJsonFormsI18n();

const jsonFormsConfig = {
restrict: false,
trim: false,
};

const renderers = [...jsonFormsRenderers];

const jsonFormsProps = computed(() => ({
renderers,
config: jsonFormsConfig,
ajv: jsonFormsAjv,
i18n: jsonFormsI18n.value,
}));
Comment on lines +109 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

LGTM: Consistent JsonForms prop consolidation pattern.

The jsonFormsProps computed centralizes JsonForms configuration, matching the pattern in ConnectSettings.standalone.vue and promoting consistency across the codebase.

🤖 Prompt for AI Agents
In web/src/components/ApiKey/ApiKeyCreate.vue around lines 109 to 121, there is
no issue — the jsonFormsProps computed consolidates JsonForms configuration
consistently; leave this code as-is to match the pattern used in
ConnectSettings.standalone.vue and no change is required.


// Use clipboard for copying
const { copyWithNotification, copied } = useClipboardWithToast();

Expand Down Expand Up @@ -464,10 +478,8 @@ const copyApiKey = async () => {
<JsonForms
:schema="formSchema.dataSchema"
:uischema="formSchema.uiSchema"
:renderers="jsonFormsRenderers"
v-bind="jsonFormsProps"
:data="formData"
:ajv="jsonFormsAjv"
:i18n="jsonFormsI18n"
@change="
({ data, errors }) => {
formData = data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ const jsonFormsConfig = {
const renderers = [...jsonFormsRenderers];
const jsonFormsI18n = useJsonFormsI18n();

const jsonFormsProps = computed(() => ({
renderers,
config: jsonFormsConfig,
ajv: jsonFormsAjv,
i18n: jsonFormsI18n.value,
}));
Comment on lines +97 to +102
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

LGTM: Consolidating JsonForms props improves maintainability.

The computed jsonFormsProps object centralizes JsonForms configuration (renderers, config, ajv, i18n) into a single source of truth, simplifying prop management and aligning with the pattern used in ApiKeyCreate.vue.

🤖 Prompt for AI Agents
In web/src/components/ConnectSettings/ConnectSettings.standalone.vue around
lines 97 to 102, the new computed jsonFormsProps consolidates renderers, config,
ajv, and i18n into a single prop object; keep this refactor as-is, ensure the
component consumers are updated to use jsonFormsProps instead of individual
props, and mirror the same pattern used in ApiKeyCreate.vue for consistency.


/** Called when the user clicks the "Apply" button */
const submitSettingsUpdate = async () => {
console.log('[ConnectSettings] trying to update settings to', formState.value);
Expand Down Expand Up @@ -122,13 +129,10 @@ const onChange = ({ data }: { data: Record<string, unknown> }) => {
<div class="mt-6 pl-3 [&_.vertical-layout]:space-y-6">
<JsonForms
v-if="settings"
v-bind="jsonFormsProps"
:schema="settings.dataSchema"
:uischema="settings.uiSchema"
:renderers="renderers"
:data="formState"
:config="jsonFormsConfig"
:ajv="jsonFormsAjv"
:i18n="jsonFormsI18n"
:readonly="isUpdating"
@change="onChange"
/>
Expand Down
82 changes: 62 additions & 20 deletions web/src/helpers/jsonforms-i18n.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
import { computed } from 'vue';
import { ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';

import type { JsonFormsI18nState } from '@jsonforms/core';

const toStringIfNeeded = (value: unknown) => {
if (typeof value === 'string') {
return value;
}
if (value == null) {
return '';
}
return String(value);
};

export function useJsonFormsI18n() {
const { t, te, locale } = useI18n();
const { t, te, locale, messages } = useI18n();

const translate: NonNullable<JsonFormsI18nState['translate']> = (id, defaultMessage, values) => {
if (id && te(id)) {
const result = t(id, values ?? {});
return toStringIfNeeded(result);
}

if (defaultMessage) {
return toStringIfNeeded(defaultMessage);
}

return id ?? '';
};

return computed<JsonFormsI18nState>(() => ({
const translateError: NonNullable<JsonFormsI18nState['translateError']> = (error) => {
const key = error.keyword ? `jsonforms.errors.${error.keyword}` : undefined;
if (key && te(key)) {
const translated = t(key, error.params ?? {});
return toStringIfNeeded(translated);
}
return error.message ?? error.keyword ?? '';
};

const state = ref<JsonFormsI18nState>({
locale: locale.value,
translate: (id, defaultMessage, values) => {
if (id && te(id)) {
const result = t(id, values);
return typeof result === 'string' ? result : String(result);
}
if (defaultMessage) {
return defaultMessage;
}
return id;
translate,
translateError,
});

watch(
locale,
(currentLocale) => {
state.value = {
locale: currentLocale,
translate,
translateError,
};
},
translateError: (error) => {
const key = error.keyword ? `jsonforms.errors.${error.keyword}` : undefined;
if (key && te(key)) {
const translated = t(key, error.params ?? {});
return typeof translated === 'string' ? translated : String(translated);
}
return error.message ?? error.keyword ?? '';
{ immediate: true }
);

watch(
() => messages.value?.[locale.value],
() => {
state.value = {
locale: locale.value,
translate,
translateError,
};
},
}));
{ deep: true }
);

return state;
}
Loading