Skip to content

Conversation

elibosley
Copy link
Member

@elibosley elibosley commented Oct 3, 2025

Summary by CodeRabbit

  • New Features

    • App-wide internationalization: dynamic locale loading, new English master bundle, and many new locale packs (ar, bn, ca, cs, da, de, es, fr, hi, hr, hu, etc.). Extraction and sorting scripts added.
  • UI

    • Brand button: keyboard activation support (Enter/Space).
  • Documentation

    • Internationalization guides added to API and Web READMEs.
  • Chores

    • Crowdin configuration, window locale exposure, and version metadata bumped.
  • Tests

    • Tests updated to use centralized i18n test helpers and loader workflow.

…ripts

- Added `crowdin.yml` for Crowdin integration to manage translation files.
- Introduced `i18n:extract` scripts in both `api` and `web` packages to automate extraction of translation keys from source files.
- Created initial English locale files in both `api/src/i18n/en.json` and `web/src/locales/en.json`.
- Updated README files to include instructions for running the extraction scripts and details on the internationalization process.
- Refactored i18n setup in the application to utilize the new extraction and loading mechanisms.

This commit enhances the application's internationalization capabilities, allowing for easier management and addition of translations.
…ing of the script

This commit cleans up the `extract-translations.mjs` file by removing an extraneous newline, improving the overall readability of the script without altering its functionality.
…mponents

- Introduced i18n keys for API key and OIDC configuration forms, improving localization capabilities.
- Updated form utility functions to support i18n integration for labels and descriptions.
- Added a new helper for managing i18n state in Vue components, ensuring consistent translation handling.
- Enhanced existing components to utilize the new i18n features, including ApiKeyCreate and ConnectSettings.
- Updated English locale files with new keys for API settings and OIDC provider configurations.

This commit significantly improves the application's internationalization, making it easier to manage translations and enhance user experience across different languages.
This commit adds a newline at the beginning of the `extract-translations.mjs` file to enhance the overall readability of the script. This change does not affect the functionality of the script.
- Added a new script `sort-translations.mjs` to automate the sorting of translation keys in locale JSON files, improving organization and maintainability.
- Updated the `i18n:extract` command in `package.json` to include the new sorting step, ensuring translations are consistently ordered after extraction.
- Refactored various components to utilize new i18n keys for improved localization, enhancing user experience across the application.

This commit significantly improves the internationalization process, making it easier to manage and maintain translation files.
…nents

- Integrated i18n support in ConnectSettings, updating labels and messages to utilize translation keys for improved user experience.
- Refactored OidcDebugLogs and LogViewerToolbar components to leverage i18n for dynamic text rendering, enhancing consistency across the application.
- Updated LogFilterInput to support localized preset filters and placeholders, improving accessibility and usability.
- Added new translation keys to the English locale file for better management of UI text.

These changes significantly improve the internationalization of the application, making it more user-friendly and adaptable to different languages.
…ponents

- Integrated i18n support in LogViewer and SingleLogViewer, updating labels, messages, and error handling to utilize translation keys for improved user experience.
- Refactored log file size formatting and loading messages to be dynamically translated, enhancing accessibility and usability.
- Updated English locale file with new translation keys for log viewer functionalities, ensuring consistent internationalization across the application.

These changes significantly improve the internationalization of the logging components, making them more user-friendly and adaptable to different languages.
- Changed translation file paths in `crowdin.yml` to use two-letter locale codes for consistency.
- Added `convert` package version 5.12.0 to `pnpm-lock.yaml` and `package.json`.
- Restored `glob` package version 11.0.3 in `package.json` after it was previously removed.

These changes enhance the localization setup and ensure proper dependency management for the project.
Copy link

claude bot commented Oct 3, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Contributor

coderabbitai bot commented Oct 3, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds comprehensive internationalization: new extraction/sorting scripts, an i18n loader and jsonforms-i18n helper, many web and api locale files, type and form-utils updates to carry i18n metadata, widespread UI text migrations to translation keys, test helpers and test updates, plus minor docs/config tweaks.

Changes

Cohort / File(s) Summary
Docs & READMEs
api/README.md, web/README.md
Add Internationalization sections describing extraction commands and runtime locale exposure.
Root & Package Scripts / CI
package.json, api/package.json, web/package.json, api/dev/configs/api.json, crowdin.yml
Add i18n scripts (extract/sort), bump api version, add Crowdin mapping and new dependencies for web.
Extraction & Sorting Scripts
api/scripts/extract-translations.mjs, web/scripts/extract-translations.mjs, web/scripts/sort-translations.mjs
New Node scripts to extract keys from source, merge/update English locale, propagate missing keys to other locales, and sort locale files.
Web i18n loader & JsonForms adapter
web/src/helpers/i18n-loader.ts, web/src/helpers/jsonforms-i18n.ts, web/src/helpers/globals.d.ts
New dynamic i18n loader (DEFAULT_LOCALE, normalize/resolve, createI18nInstance, ensureLocale, getWindowLocale) and JsonForms i18n composition helper; Window LOCALE typing added.
Web mount/init & tests
web/src/components/Wrapper/mount-engine.ts, web/__test__/components/Wrapper/mount-engine.test.ts, web/__test__/utils/i18n.ts
Replace LOCALE_DATA parsing with loader-based initialization; add test i18n helpers and update tests to use createTestI18n/testTranslate.
JsonForms wiring & UI usage
web/src/components/ApiKey/ApiKeyCreate.vue, web/src/components/ConnectSettings/*.vue
Wire jsonforms-i18n into JsonForms instances and update modal titles/strings to namespaced keys.
API types & form utilities
api/src/unraid-api/types/json-forms.ts, api/src/unraid-api/graph/utils/form-utils.ts
Add I18nJsonSchema type (optional i18n) and add optional i18nKey parameter to labeled control factory functions (signatures updated).
API UI schema i18n metadata
api/src/unraid-api/graph/resolvers/* (api-key, settings, sso/oidc-config.service.ts)
Add i18n key constants and attach i18n metadata to UI schema controls (no behavior change).
API locale placeholders
api/src/i18n/*.json
Add many empty locale JSON files as placeholders for API (various locales).
Web locales dataset
web/src/locales/en.json, web/src/locales/*.json, removed web/src/locales/en_US.json, removed _template.json
Add comprehensive en.json and many locale bundles (ar, bn, ca, cs, da, de, es, fr, hi, hr, hu, etc.); delete en_US.json and the template file.
UI components — i18n migration
web/src/components/** (Activation, Logs, UpdateOs, Registration, UserProfile, Notifications, ConnectSettings, sso, Modal, etc.)
Replace hard-coded text with namespaced translation keys and use useI18n where applicable; small prop/type updates in a few components.
Logs improvements
web/src/components/Logs/*
Integrate i18n into logs UI, add translated presets/highlight options, improved file-size formatting, and language auto-detection.
UI library & form layout tweaks
unraid-ui/src/components/brand/BrandButton.vue, unraid-ui/src/forms/AccordionLayout.vue
BrandButton: typed emits and keyboard activation handlers; AccordionLayout: typing/import adjustments and template typing refinements.
Plugin translation removal & locale exposure
plugin/source/.../include/translations.php, plugin/source/.../include/myservers2.php, plugin/plugins/dynamix.unraid.net.plg
Remove WebComponentTranslations implementation; expose LOCALE (string) to frontend and stop backing up translations.php.
Tests — broad updates
web/__test__/**/*
Add test i18n helper and update many tests to mount with i18n or use testTranslate; replace older vue-i18n mocks.
Type augmentations
api/src/types/jsonforms-i18n.d.ts
Add module augmentations to allow optional i18n?: string on json schema types.
Lint config
api/.eslintrc.ts
Adjust import/extensions rule to include mjs/cjs and set ts/tsx to 'never'.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as Web App
  participant Loader as i18n-loader
  participant Bundles as Locale Bundles (import.meta.glob)
  participant VueI18n as Vue I18n

  User->>App: open page
  App->>Loader: createI18nInstance()
  Loader-->>App: i18n instance (locale=DEFAULT_LOCALE)
  App->>Loader: ensureLocale(getWindowLocale())
  Loader->>Loader: resolveLocale(candidate)
  alt target locale not loaded
    Loader->>Bundles: dynamic import(locale)
    Bundles-->>Loader: messages
    Loader->>VueI18n: setLocaleMessage(locale, messages)
  else already available
    Loader-->>App: locale already available
  end
  Loader->>VueI18n: set active locale
  App-->>User: render UI with translations
  note right of Loader: on failure -> fallback to DEFAULT_LOCALE
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Poem

I nibble keys in fields of i18n light,
Hop from en to ar through the night.
Scripts gather phrases, neat and small,
Loaders fetch tongues to dress them all.
A rabbit hums: translations take flight. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: full i18n” accurately conveys that this pull request introduces comprehensive internationalization support across the codebase, directly reflecting the extensive i18n-related changes in both API and web projects.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/i18n

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

github-actions bot commented Oct 3, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

Comment on lines 149 to 155

<div class="prose text-base leading-relaxed whitespace-normal opacity-75">
<p>
{{
t(
'Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.'
)
}}
{{ t('updateOs.update.receiveTheLatestAndGreatestFor') }}
</p>
<p v-if="available">
{{ flashBackupCopy }}

Choose a reason for hiding this comment

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

P1 Badge Provide translation function to template

The template calls t('updateOs.update.receiveTheLatestAndGreatestFor'), but t is never defined in <script setup>. Here defineProps is assigned to props, so Vue does not auto-expose individual props to the template. At runtime this will throw ReferenceError: t is not defined, breaking the Update panel. Either destructure the prop (const { t } = defineProps…) or reference props.t from the template.

Useful? React with 👍 / 👎.

Comment on lines 63 to 83
<ArrowUturnDownIcon class="w-5 shrink-0" />
<span class="inline-flex flex-wrap items-baseline justify-start gap-2 leading-none">
<span class="text-xl">
{{ t('Downgrade Unraid OS to {0}', [version]) }}
{{ t('updateOs.downgrade.downgradeUnraidOsTo', [version]) }}
</span>
<span
v-if="releaseDate && formattedReleaseDate !== 'Invalid Date'"
class="shrink text-base opacity-75"
>
{{ t('Original release date {0}', [formattedReleaseDate]) }}
{{ t('updateOs.downgrade.originalReleaseDate', [formattedReleaseDate]) }}
</span>
</span>
</h3>
<div class="prose text-base leading-relaxed whitespace-normal opacity-75">
<p>{{ t(`Downgrades are only recommended if you're unable to solve a critical issue.`) }}</p>
<p>{{ t('updateOs.downgrade.downgradesAreOnlyRecommendedIfYou') }}</p>
<p>
{{
t(
'In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.'
)
}}
{{ t('updateOs.downgrade.inTheRareEventYouNeed') }}
</p>
<p>
{{
t(
'Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.'
)
}}
{{ t('updateOs.downgrade.downloadTheDiagnosticsZipThenPlease') }}
</p>

Choose a reason for hiding this comment

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

P1 Badge Destructure t before using in downgrade template

The Downgrade view also assigns defineProps to props but the template invokes t('updateOs.downgrade…') directly. Without a const { t } = props or useI18n, t is undefined and the component will fail to render whenever the downgrade UI is shown. Expose the prop before using it or reference props.t in the template.

Useful? React with 👍 / 👎.

Comment on lines 113 to 117
:icon="EyeIcon"
:icon-right="ArrowTopRightOnSquareIcon"
:name="updateButton?.name"
:text="t('View Changelog')"
:text="t('updateOs.updateIneligible.viewChangelog')"
class="flex-none"

Choose a reason for hiding this comment

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

P1 Badge Fix undefined t in update-ineligible CTA

Similar to the other Update components, UpdateIneligible.vue stores the props object in props but references t('updateOs.updateIneligible…') directly in the template. Vue will not inject t automatically in this case, so these bindings will throw when the component renders. Destructure t from props or use props.t to avoid the runtime error.

Useful? React with 👍 / 👎.

Comment on lines 188 to 197
>
{{
available
? t('Unraid {0} Available', [available])
? t('updateOs.status.unraidAvailable', [available])
: availableWithRenewal
? t('Up-to-date with eligible releases')
: t('Up-to-date')
? t('updateOs.status.upToDateWithEligibleReleases')
: t('updateOs.status.upToDate')
}}
</Badge>
<Badge

Choose a reason for hiding this comment

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

P1 Badge Ensure t is defined before using in status badges

The status component references t('updateOs.status…') throughout the template but only defines const props = defineProps… in script. Because the prop isn’t destructured, t isn’t available in the template scope and rendering this component raises a t is not defined error. Extract t from props or rewrite template references to props.t.

Useful? React with 👍 / 👎.

Comment on lines 150 to 156
>
<ResponsiveModalHeader>
<ResponsiveModalTitle>
{{ t('Unraid OS {0} Changelog', [currentRelease.version]) }}
{{ t('updateOs.changelogModal.unraidOsChangelog', [currentRelease.version]) }}
</ResponsiveModalTitle>
</ResponsiveModalHeader>

Choose a reason for hiding this comment

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

P1 Badge Changelog modal template uses undefined t

ChangelogModal.vue sets const props = defineProps but the modal title uses {{ t('updateOs.changelogModal…') }}. Because t is only available on props, this expression will crash when opening the changelog modal. Destructure t (const { t } = props) or switch template bindings to props.t.

Useful? React with 👍 / 👎.

Comment on lines 406 to 410
<TooltipTrigger as-child>
<Button variant="ghost" @click="accountStore.updateOs()">
<ArrowTopRightOnSquareIcon class="mr-2 h-4 w-4" />
{{ t('More Options') }}
{{ t('updateOs.checkUpdateResponseModal.moreOptions') }}
</Button>

Choose a reason for hiding this comment

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

P1 Badge Check-update modal references t without exposing it

CheckUpdateResponseModal.vue expects t via props but binds {{ t('updateOs.checkUpdateResponseModal…') }} in the template. Since the script assigns the props to const props and never destructures the translation function, Vue will throw when the modal renders. Destructure the prop or replace usages with props.t(...).

Useful? React with 👍 / 👎.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (10)
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php (6)

431-435: Critical: json_encode flags misused; UNESCAPED_UNICODE not applied

The third argument to json_encode is depth, not an additional flag. Unicode will be escaped unintentionally. OR the flags in the second arg and omit the third arg.

Apply this diff:

-        if ($urlEncode) {
-            return rawurlencode(json_encode($this->getTranslations(), JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE));
-        }
-        return json_encode($this->getTranslations(), JSON_UNESCAPED_SLASHES, JSON_UNESCAPED_UNICODE);
+        if ($urlEncode) {
+            return rawurlencode(json_encode($this->getTranslations(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
+        }
+        return json_encode($this->getTranslations(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

78-78: Stray '>' in HTML output

There’s an extra '>' inside the closing list item; results in a visible stray character in UI.

Apply this diff:

-            '... 5 minutes') . '</li><li>' . _('Please make sure there is a DNS server specified') . '</li>>' . '</ul>',
+            '... 5 minutes') . '</li><li>' . _('Please make sure there is a DNS server specified') . '</li>' . '</ul>',

200-200: Typo in key: "It\s" → "It's"

Backslash-s appears instead of an apostrophe in the key; inconsistent with the value and likely with frontend keys.

Apply this diff:

-            'It\s highly recommended to review the changelog before continuing your update.' => _('It\'s highly recommended to review the changelog before continuing your update.'),
+            'It\'s highly recommended to review the changelog before continuing your update.' => _('It\'s highly recommended to review the changelog before continuing your update.'),

219-219: Mismatch: "Unraidnet" vs "Unraid.net"

Dot missing in the translated string.

Apply this diff:

-            'Linked to Unraid.net account' => _('Linked to Unraidnet account'),
+            'Linked to Unraid.net account' => _('Linked to Unraid.net account'),

82-82: Spelling: "Acklowledge" → "Acknowledge"

User-facing typo.

Apply this diff:

-            'Acklowledge that you have made a Flash Backup to enable this action' => _('Acklowledge that you have made a Flash Backup to enable this action'),
+            'Acknowledge that you have made a Flash Backup to enable this action' => _('Acknowledge that you have made a Flash Backup to enable this action'),

273-273: Grammar: "Whether it new features" → "Whether it's new features"

Minor copy fix.

Apply this diff:

-            'Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.' => _('Receive the latest and greatest for Unraid OS.') . ' ' . _('Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.'),
+            'Receive the latest and greatest for Unraid OS. Whether it\'s new features, security patches, or bug fixes — keeping your server up-to-date ensures the best experience that Unraid has to offer.' => _('Receive the latest and greatest for Unraid OS.') . ' ' . _('Whether it\'s new features, security patches, or bug fixes — keeping your server up-to-date ensures the best experience that Unraid has to offer.'),
web/src/components/Activation/WelcomeModal.standalone.vue (1)

40-44: SSR safety: guard window access

Accessing window during setup can crash on SSR. Guard it.

-const isLoginPage = computed(() => window.location.pathname.includes('login'));
-// Initialize showModal based on conditions
-const showModal = ref(isLoginPage.value || isInitialSetup.value);
+const isClient = typeof window !== 'undefined';
+const isLoginPage = computed(() => (isClient ? window.location.pathname.includes('login') : false));
+// Initialize showModal based on conditions
+const showModal = ref((isLoginPage.value) || isInitialSetup.value);
api/src/unraid-api/graph/resolvers/api-key/api-key-form.service.ts (1)

148-176: Fix CI: enum must be string[]; Resource[] breaks JsonSchema typing

TypeScript fails because enum expects string[] but getAvailableResources() returns Resource[].

Apply this fix to return string[] safely and satisfy JsonSchema types:

@@
-    private getAvailableResources(): Resource[] {
-        return Object.values(Resource);
-    }
+    // Ensure JSON Schema enums are string arrays
+    private getAvailableResources(): string[] {
+        return Object.values(Resource).filter((v): v is string => typeof v === 'string');
+    }

No changes needed where it’s used; the schema already declares items.type = 'string'.

Optionally do the same for getAvailableRoles() for consistency.

web/src/components/UpdateOs/CheckUpdateResponseModal.vue (1)

361-373: Avoid double-translation in extraLinks.

extraLinks sets text to a translated string, but the template calls t(item.text) again.

-              :text="t(item.text ?? '')"
-              :title="item.title ? t(item.title) : undefined"
+              :text="item.text ?? ''"
+              :title="item.title ? item.title : undefined"

Alternatively, store keys in extraLinks and translate in the template consistently.

web/src/components/Logs/SingleLogViewer.vue (1)

403-407: Fix canLoadMore predicate: use computed value

Passing the computed itself returns a truthy ref, not a boolean. Use the ref’s value.

-        { direction: 'top', distance: 200, canLoadMore: () => shouldLoadMore },
+        { direction: 'top', distance: 200, canLoadMore: () => shouldLoadMore.value },
🧹 Nitpick comments (25)
web/src/components/sso/useSsoAuth.ts (1)

2-2: Add .js extension to import for ESM compatibility.

The import from vue-i18n lacks the .js extension required by the coding guidelines for TypeScript ESM compatibility.

As per coding guidelines, apply this diff:

-import { useI18n } from 'vue-i18n';
+import { useI18n } from 'vue-i18n.js';
web/src/components/Notifications/Item.vue (1)

125-125: Error message placement may not match expected behavior.

The learning from PR #941 specifies that when delete or archive mutations fail, the error message should be shown below the action buttons. Currently, the error is displayed on line 125, which appears above the buttons div that starts on line 127.

Consider moving the error display to after the closing </div> tag of the buttons section to match the expected behavior.

Based on learnings.

Apply this diff to move the error below the action buttons:

-    <p v-if="mutationError" class="text-red-600">{{ t('common.error') }}: {{ mutationError }}</p>
-
     <div class="flex items-baseline justify-end gap-4">
       <a
         v-if="link"
@@ -148,6 +146,8 @@
         <span class="text-sm">{{ t('notifications.item.delete') }}</span>
       </Button>
     </div>
+
+    <p v-if="mutationError" class="text-red-600">{{ t('common.error') }}: {{ mutationError }}</p>
   </div>
 </template>
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php (1)

52-52: Use positional specifiers in translatable strings: %1$s, %2$s

To support languages that reorder placeholders, prefer %1$s, %2$s, etc., in translatable strings instead of bare %s or width-based %1s/%2s.

Example:

-            '{0} {1} Key…' => sprintf(_('%1s %2s Key…'), '{0}', '{1}'),
+            '{0} {1} Key…' => sprintf(_('%1$s %2$s Key…'), '{0}', '{1}'),
-            '{1} Key {0} Successfully' => sprintf(_('%2s Key %1s Successfully'), '{0}', '{1}'),
+            '{1} Key {0} Successfully' => sprintf(_('%2$s Key %1$s Successfully'), '{0}', '{1}'),

Also applies to: 60-60

api/src/i18n/ar.json (1)

1-1: Confirm BCP‑47 code choice if variants needed.

Using generic "ar" is fine; if region-specific (e.g., ar-SA, ar-EG) are planned, document fallback/aliasing.

api/src/i18n/de.json (1)

1-1: LGTM: locale scaffold.

No issues. Keep consistent formatting (minified JSON) across locales for smaller diffs.

api/src/i18n/ca.json (1)

1-1: LGTM: locale scaffold.

All good. If you plan key ordering, ensure the sorter (if any) is applied on API locales too, not only web.

web/src/locales/hu.json (1)

1-603: LGTM! Comprehensive Hungarian locale addition.

The locale file provides extensive translation coverage across all application namespaces. The use of placeholders ({0}, {n}) and HTML markup in translations is standard practice for i18n frameworks like vue-i18n.

Optional: Consider HTML-free translations where possible.

Some translations embed HTML markup (e.g., lines 304, 349, 371), which can complicate maintenance if the markup structure needs to change across all locales. Where feasible, consider using component-level markup with translation keys for text content only. This is not blocking and can be addressed incrementally.

web/src/components/DownloadApiLogs.standalone.vue (1)

21-23: Consider consolidating multiple t() calls in paragraph.

The paragraph uses three separate t() calls (lines 21-23). While this provides translation flexibility, it may result in awkward sentence flow in some languages. Consider whether these could be combined into a single translation key with proper sentence structure, or if the current approach is intentional for translation purposes.

web/src/components/UserProfile/DropdownContent.vue (1)

81-83: Good migration to namespaced i18n keys.

Keys and positional params look consistent with locales. One nit: showKeyline computes a non-boolean (number/object). Consider coercing to boolean for clarity.

Example minimal change:

-const showKeyline = computed(
-  () =>
-    (showConnectStatus.value && (keyActions.value?.length || links.value.length)) ||
-    unraidConnectWelcome.value
-);
+const showKeyline = computed(() =>
+  !!(
+    (showConnectStatus.value && (keyActions.value?.length || links.value.length)) ||
+    unraidConnectWelcome.value
+  )
+);

Also applies to: 93-94, 105-107, 118-120, 145-147, 162-164, 172-174, 196-198

web/src/components/Registration/KeyLinkedStatus.vue (1)

37-42: Use a localized fallback instead of 'Unknown'.

Prefer t('common.unknown') over t('Unknown') to ensure proper localization.

-{{ t(keyLinkedOutput.text ?? 'Unknown') }}
+{{ t(keyLinkedOutput.text ?? 'common.unknown') }}

Apply the same change at both occurrences.

web/src/components/Activation/WelcomeModal.standalone.vue (1)

34-36: Prefer named params to avoid list-order bugs

Use object params for clarity and to match typical message templates.

-  partnerInfo.value?.partnerName
-    ? t('activation.welcomeModal.welcomeToYourNewSystemPowered', [partnerInfo.value?.partnerName])
+  partnerInfo.value?.partnerName
+    ? t('activation.welcomeModal.welcomeToYourNewSystemPowered', { partnerName: partnerInfo.value?.partnerName })
     : t('activation.welcomeModal.welcomeToUnraid')
web/src/components/ApiKey/ApiKeyCreate.vue (1)

438-442: Remove redundant t() fallback

t always exists from useI18n(). Simplify and ensure all branches are localized.

-            ? 'Authorize API Key Access'
-            : editingKey
-              ? t
-                ? t('apiKey.apiKeyCreate.editApiKey')
-                : 'Edit API Key'
-              : t
-                ? t('apiKey.apiKeyCreate.createApiKey')
-                : 'Create API Key'
+            ? t('apiKey.apiKeyCreate.authorizeAccess')
+            : editingKey
+              ? t('apiKey.apiKeyCreate.editApiKey')
+              : t('apiKey.apiKeyCreate.createApiKey')

Note: add apiKey.apiKeyCreate.authorizeAccess to locales.

api/src/unraid-api/types/json-forms.ts (1)

17-22: Optional: allow params for schema-level i18n

If you plan parameterized labels, consider supporting an object.

-export type I18nJsonSchema = JsonSchema & {
-    i18n?: string;
-};
+export type I18nJsonSchema = JsonSchema & {
+    i18n?: string | { key: string; params?: Record<string, unknown> };
+};
web/src/components/ConnectSettings/ConnectSettings.standalone.vue (1)

118-118: Include punctuation in the translation

Avoid appending “:” outside the i18n key so locales can control punctuation.

-<Label>{{ t('downloadApiLogs.downloadUnraidApiLogs') }}:</Label>
+<Label>{{ t('downloadApiLogs.downloadUnraidApiLogsWithColon') }}</Label>

Or include the colon in the existing key’s message.

web/src/components/UpdateOs/UpdateIneligible.vue (1)

53-59: Remove redundant conditional in watchEffect.

Both branches do the same thing.

-watchEffect(() => {
-  if (availableWithRenewal.value) {
-    updateButton.value = updateOsActionsStore.updateCallbackButton();
-  } else {
-    updateButton.value = updateOsActionsStore.updateCallbackButton();
-  }
-});
+watchEffect(() => {
+  updateButton.value = updateOsActionsStore.updateCallbackButton();
+});
web/src/helpers/jsonforms-i18n.ts (1)

9-29: Tighten types for translate/translateError to avoid implicit any.

Align with JsonForms expectations and our TS guidelines.

As per coding guidelines:

-    translate: (id, defaultMessage, values) => {
+    translate: (id: string, defaultMessage?: string, values?: Record<string, unknown> | unknown[]) => {
       if (id && te(id)) {
         const result = t(id, values);
         return typeof result === 'string' ? result : String(result);
       }
       if (defaultMessage) {
         return defaultMessage;
       }
       return id;
     },
-    translateError: (error) => {
+    translateError: (error: { keyword?: string; message?: string; params?: Record<string, unknown> }) => {
       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 ?? '';
     },
web/__test__/components/Wrapper/mount-engine.test.ts (1)

151-188: Test doesn’t verify JSON prop parsing behavior.

The test asserts the attribute value, not that the component received parsed JSON. Consider asserting rendered output or adjusting the test component to accept/display structured input.

Example: change TestComponent prop type to [String, Object] and render JSON.stringify when object, then assert rendered text.

web/src/components/UpdateOs/CheckUpdateResponseModal.vue (1)

318-321: Untranslated literal: “Released on …”.

Localize this string to keep UI fully i18n.

-              <p v-if="userFormattedReleaseDate" class="text-muted-foreground mt-2 text-center text-sm">
-                Released on {{ userFormattedReleaseDate }}
-              </p>
+              <p v-if="userFormattedReleaseDate" class="text-muted-foreground mt-2 text-center text-sm">
+                {{ t('updateOs.checkUpdateResponseModal.releasedOn', [userFormattedReleaseDate]) }}
+              </p>

Ensure the key exists in locales.

web/package.json (1)

73-73: Update vue-i18n-extract to the latest 2.x patch version

  • Bump vue-i18n-extract from 2.0.4 to 2.0.7
web/src/components/Registration/UpdateExpirationAction.vue (1)

41-44: Avoid concatenating translation strings; use a single key with interpolation

Concatenation breaks grammar in many locales. Prefer one key that includes the whole sentence with placeholders.

Example:

  • Define a single message like registration.updateExpirationAction.eligibleForUpdatesReleasedOnOrAndExtend with a {date} placeholder.
  • Use: props.t('registration.updateExpirationAction.eligibleForUpdatesReleasedOnOrAndExtend', { date: formattedRegExp.value })
web/src/components/UpdateOs/Update.vue (1)

66-82: Prefer full-sentence translation keys over assembling paragraphs

Building paragraphs by concatenating multiple t() calls risks awkward spacing/grammar and complicates localization.

  • Create complete strings per branch, e.g.:
    • updateOs.update.flashBackup.copyAlreadyActivated
    • updateOs.update.flashBackup.copyNotActivated
    • updateOs.update.flashBackup.copyManualOnly
  • Each key should include all sentences in the branch, with placeholders as needed.
web/src/components/Logs/LogViewer.standalone.vue (2)

7-7: Verify convert API and tighten typing for formatFileSize.

Ensure convert(bytes, 'B').to('best', 'metric') exists and returns { quantity, unit } as expected. Current (best.quantity as number) hints missing types.

Consider a small type guard to avoid casts:

+type BestSize = { quantity: number; unit: string };
+const isBestSize = (v: unknown): v is BestSize =>
+  !!v && typeof (v as any).quantity === 'number' && typeof (v as any).unit === 'string';

-    const best = convert(bytes, 'B').to('best', 'metric');
-    const formattedValue = new Intl.NumberFormat(undefined, {
+    const res = convert(bytes, 'B').to('best', 'metric');
+    if (!isBestSize(res)) throw new Error('Unexpected convert() result');
+    const formattedValue = new Intl.NumberFormat(undefined, {
       maximumFractionDigits: 2,
       minimumFractionDigits: 0,
-    }).format(best.quantity as number);
+    }).format(res.quantity);
-
-    const unit = unitLabels.value[best.unit] ?? best.unit;
+    const unit = unitLabels.value[res.unit] ?? res.unit;

If convert doesn’t guarantee “metric” symbols (e.g., returns kB vs KB), extend unitLabels or normalize the unit key before lookup.

Also applies to: 75-82, 91-104


218-219: Avoid exposing raw backend error messages to users.

Interpolating logFilesError.message can leak internal details. Prefer a generic message and log details to console/telemetry.

-        {{ t('logs.viewer.errorLoadingLogFiles', { error: logFilesError.message }) }}
+        {{ t('logs.viewer.errorLoadingLogFiles') }}
web/src/helpers/i18n-loader.ts (2)

43-47: Normalize by comparing base language parts.

Current check compares full candidate (e.g., en_US) to base (en) and never matches. Compare base segments.

-  for (const candidate of supportedLocaleCache) {
-    if (candidate.toLowerCase() === base.toLowerCase()) {
-      return candidate;
-    }
-  }
+  for (const candidate of supportedLocaleCache) {
+    const candidateBase = candidate.split(/[-_]/)[0];
+    if (candidateBase.toLowerCase() === base.toLowerCase()) {
+      return candidate;
+    }
+  }

101-115: Remove unsafe casts and unnecessary mutation of availableLocales.

No need to coerce types or push into the array; setLocaleMessage makes the locale available.

-  const availableLocales = i18n.global.availableLocales as unknown as string[];
-
-  if (!availableLocales.includes(targetLocale)) {
+  const hasLocale = (i18n.global.availableLocales as readonly string[]).includes(targetLocale);
+
+  if (!hasLocale) {
     const messages = await loadLocaleMessages(targetLocale);
     if (!messages) {
       i18n.global.locale.value = DEFAULT_LOCALE;
       return DEFAULT_LOCALE;
     }
-    i18n.global.setLocaleMessage(targetLocale as typeof DEFAULT_LOCALE, messages);
-    availableLocales.push(targetLocale);
+    i18n.global.setLocaleMessage(targetLocale, messages);
   }
 
-  i18n.global.locale.value = targetLocale as typeof DEFAULT_LOCALE;
+  i18n.global.locale.value = targetLocale;
   return targetLocale;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed594e9 and 7583331.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (102)
  • api/README.md (1 hunks)
  • api/dev/configs/api.json (1 hunks)
  • api/package.json (1 hunks)
  • api/scripts/extract-translations.mjs (1 hunks)
  • api/src/i18n/ar.json (1 hunks)
  • api/src/i18n/ca.json (1 hunks)
  • api/src/i18n/cs.json (1 hunks)
  • api/src/i18n/da.json (1 hunks)
  • api/src/i18n/de.json (1 hunks)
  • api/src/i18n/en.json (1 hunks)
  • api/src/i18n/es.json (1 hunks)
  • api/src/i18n/fr.json (1 hunks)
  • api/src/i18n/hu.json (1 hunks)
  • api/src/i18n/it.json (1 hunks)
  • api/src/i18n/ja.json (1 hunks)
  • api/src/i18n/ko.json (1 hunks)
  • api/src/i18n/nl.json (1 hunks)
  • api/src/i18n/no.json (1 hunks)
  • api/src/i18n/pl.json (1 hunks)
  • api/src/i18n/pt.json (1 hunks)
  • api/src/i18n/ro.json (1 hunks)
  • api/src/i18n/ru.json (1 hunks)
  • api/src/i18n/sv.json (1 hunks)
  • api/src/unraid-api/graph/resolvers/api-key/api-key-form.service.ts (13 hunks)
  • api/src/unraid-api/graph/resolvers/settings/settings.service.ts (3 hunks)
  • api/src/unraid-api/graph/resolvers/sso/core/oidc-config.service.ts (32 hunks)
  • api/src/unraid-api/graph/utils/form-utils.ts (3 hunks)
  • api/src/unraid-api/types/json-forms.ts (1 hunks)
  • crowdin.yml (1 hunks)
  • package.json (1 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers2.php (1 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php (1 hunks)
  • unraid-ui/src/components/brand/BrandButton.vue (3 hunks)
  • unraid-ui/src/forms/AccordionLayout.vue (7 hunks)
  • web/README.md (1 hunks)
  • web/__test__/components/Wrapper/mount-engine.test.ts (3 hunks)
  • web/package.json (4 hunks)
  • web/scripts/extract-translations.mjs (1 hunks)
  • web/scripts/sort-translations.mjs (1 hunks)
  • web/src/components/Activation/ActivationModal.vue (3 hunks)
  • web/src/components/Activation/WelcomeModal.standalone.vue (2 hunks)
  • web/src/components/ApiKey/ApiKeyCreate.vue (4 hunks)
  • web/src/components/ConnectSettings/ConnectSettings.standalone.vue (7 hunks)
  • web/src/components/ConnectSettings/OidcDebugLogs.vue (3 hunks)
  • web/src/components/DowngradeOs.standalone.vue (2 hunks)
  • web/src/components/DownloadApiLogs.standalone.vue (5 hunks)
  • web/src/components/HeaderOsVersion.standalone.vue (7 hunks)
  • web/src/components/Logs/LogFilterInput.vue (4 hunks)
  • web/src/components/Logs/LogViewer.standalone.vue (6 hunks)
  • web/src/components/Logs/LogViewerToolbar.vue (5 hunks)
  • web/src/components/Logs/SingleLogViewer.vue (9 hunks)
  • web/src/components/Modal.vue (2 hunks)
  • web/src/components/Notifications/Item.vue (4 hunks)
  • web/src/components/Notifications/List.vue (4 hunks)
  • web/src/components/Notifications/Sidebar.vue (10 hunks)
  • web/src/components/Registration.standalone.vue (11 hunks)
  • web/src/components/Registration/KeyLinkedStatus.vue (3 hunks)
  • web/src/components/Registration/ReplaceCheck.vue (2 hunks)
  • web/src/components/Registration/UpdateExpiration.vue (1 hunks)
  • web/src/components/Registration/UpdateExpirationAction.vue (4 hunks)
  • web/src/components/UpdateOs.standalone.vue (2 hunks)
  • web/src/components/UpdateOs/CallbackButton.vue (1 hunks)
  • web/src/components/UpdateOs/ChangelogModal.vue (3 hunks)
  • web/src/components/UpdateOs/CheckUpdateResponseModal.vue (14 hunks)
  • web/src/components/UpdateOs/Downgrade.vue (4 hunks)
  • web/src/components/UpdateOs/IgnoredRelease.vue (1 hunks)
  • web/src/components/UpdateOs/RawChangelogRenderer.vue (3 hunks)
  • web/src/components/UpdateOs/Status.vue (11 hunks)
  • web/src/components/UpdateOs/ThirdPartyDrivers.vue (1 hunks)
  • web/src/components/UpdateOs/Update.vue (6 hunks)
  • web/src/components/UpdateOs/UpdateIneligible.vue (4 hunks)
  • web/src/components/UserProfile.standalone.vue (2 hunks)
  • web/src/components/UserProfile/CallbackFeedback.vue (10 hunks)
  • web/src/components/UserProfile/DropdownConnectStatus.vue (1 hunks)
  • web/src/components/UserProfile/DropdownContent.vue (8 hunks)
  • web/src/components/UserProfile/DropdownLaunchpad.vue (1 hunks)
  • web/src/components/UserProfile/DropdownTrigger.vue (1 hunks)
  • web/src/components/UserProfile/ServerState.vue (2 hunks)
  • web/src/components/UserProfile/Trial.vue (2 hunks)
  • web/src/components/UserProfile/UptimeExpire.vue (1 hunks)
  • web/src/components/WanIpCheck.standalone.vue (2 hunks)
  • web/src/components/Wrapper/mount-engine.ts (3 hunks)
  • web/src/components/sso/SsoButtons.vue (1 hunks)
  • web/src/components/sso/useSsoAuth.ts (1 hunks)
  • web/src/composables/dateTime.ts (1 hunks)
  • web/src/helpers/globals.d.ts (1 hunks)
  • web/src/helpers/i18n-loader.ts (1 hunks)
  • web/src/helpers/jsonforms-i18n.ts (1 hunks)
  • web/src/locales/_template.json (0 hunks)
  • web/src/locales/ar.json (1 hunks)
  • web/src/locales/bn.json (1 hunks)
  • web/src/locales/ca.json (1 hunks)
  • web/src/locales/cs.json (1 hunks)
  • web/src/locales/da.json (1 hunks)
  • web/src/locales/de.json (1 hunks)
  • web/src/locales/en.json (1 hunks)
  • web/src/locales/en_US.json (0 hunks)
  • web/src/locales/es.json (1 hunks)
  • web/src/locales/fr.json (1 hunks)
  • web/src/locales/hi.json (1 hunks)
  • web/src/locales/hr.json (1 hunks)
  • web/src/locales/hu.json (1 hunks)
💤 Files with no reviewable changes (2)
  • web/src/locales/_template.json
  • web/src/locales/en_US.json
🧰 Additional context used
📓 Path-based instructions (9)
**/components/**/*.vue

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Some Vue files may require explicit imports like ref or computed due to Nuxt auto-imports not applying in tests

Files:

  • web/src/components/Notifications/Item.vue
  • web/src/components/UpdateOs/Downgrade.vue
  • web/src/components/UserProfile/DropdownLaunchpad.vue
  • web/src/components/Activation/ActivationModal.vue
  • web/src/components/UserProfile/DropdownTrigger.vue
  • web/src/components/ApiKey/ApiKeyCreate.vue
  • web/src/components/UserProfile/CallbackFeedback.vue
  • web/src/components/UserProfile/DropdownContent.vue
  • web/src/components/Notifications/Sidebar.vue
  • web/src/components/HeaderOsVersion.standalone.vue
  • web/src/components/Registration/UpdateExpiration.vue
  • web/src/components/WanIpCheck.standalone.vue
  • web/src/components/Modal.vue
  • web/src/components/Registration/KeyLinkedStatus.vue
  • web/src/components/UpdateOs/CallbackButton.vue
  • web/src/components/UserProfile/ServerState.vue
  • web/src/components/UpdateOs/ThirdPartyDrivers.vue
  • web/src/components/Notifications/List.vue
  • web/src/components/DowngradeOs.standalone.vue
  • web/src/components/Registration.standalone.vue
  • web/src/components/UpdateOs/RawChangelogRenderer.vue
  • web/src/components/DownloadApiLogs.standalone.vue
  • web/src/components/UserProfile/Trial.vue
  • unraid-ui/src/components/brand/BrandButton.vue
  • web/src/components/Registration/UpdateExpirationAction.vue
  • web/src/components/UpdateOs/ChangelogModal.vue
  • web/src/components/UpdateOs/Status.vue
  • web/src/components/UpdateOs/UpdateIneligible.vue
  • web/src/components/sso/SsoButtons.vue
  • web/src/components/Registration/ReplaceCheck.vue
  • web/src/components/Logs/LogFilterInput.vue
  • web/src/components/UpdateOs/CheckUpdateResponseModal.vue
  • web/src/components/UserProfile.standalone.vue
  • web/src/components/ConnectSettings/ConnectSettings.standalone.vue
  • web/src/components/UpdateOs/Update.vue
  • web/src/components/Logs/LogViewerToolbar.vue
  • web/src/components/Activation/WelcomeModal.standalone.vue
  • web/src/components/Logs/LogViewer.standalone.vue
  • web/src/components/UpdateOs/IgnoredRelease.vue
  • web/src/components/UserProfile/DropdownConnectStatus.vue
  • web/src/components/ConnectSettings/OidcDebugLogs.vue
  • web/src/components/Logs/SingleLogViewer.vue
  • web/src/components/UserProfile/UptimeExpire.vue
  • web/src/components/UpdateOs.standalone.vue
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • web/src/components/sso/useSsoAuth.ts
  • api/src/unraid-api/types/json-forms.ts
  • web/src/helpers/globals.d.ts
  • web/src/helpers/i18n-loader.ts
  • api/src/unraid-api/graph/resolvers/api-key/api-key-form.service.ts
  • api/src/unraid-api/graph/utils/form-utils.ts
  • api/src/unraid-api/graph/resolvers/settings/settings.service.ts
  • web/src/composables/dateTime.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/src/helpers/jsonforms-i18n.ts
  • web/src/components/Wrapper/mount-engine.ts
  • api/src/unraid-api/graph/resolvers/sso/core/oidc-config.service.ts
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/types/json-forms.ts
  • api/src/unraid-api/graph/resolvers/api-key/api-key-form.service.ts
  • api/src/unraid-api/graph/utils/form-utils.ts
  • api/src/unraid-api/graph/resolvers/settings/settings.service.ts
  • api/src/unraid-api/graph/resolvers/sso/core/oidc-config.service.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/types/json-forms.ts
  • api/src/unraid-api/graph/resolvers/api-key/api-key-form.service.ts
  • api/src/unraid-api/graph/utils/form-utils.ts
  • api/src/unraid-api/graph/resolvers/settings/settings.service.ts
  • api/src/unraid-api/graph/resolvers/sso/core/oidc-config.service.ts
{**/*.test.ts,**/__test__/{components,store}/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

{**/*.test.ts,**/__test__/{components,store}/**/*.ts}: Use .rejects.toThrow() without arguments when asserting that async functions throw; avoid checking exact error message strings unless the message format is explicitly under test
Focus tests on observable behavior and outcomes, not implementation details such as exact error messages
Use await nextTick() for DOM update assertions and flushPromises() for complex async chains; always await async operations before asserting
Place module mock declarations (vi.mock) at the top level of the test file to avoid hoisting issues
Use factory functions in vi.mock calls to define mocks and avoid hoisting pitfalls
Use vi.spyOn() to specify return values or behavior of methods under test
Reset/clear mocks between tests using vi.clearAllMocks() (and vi.resetAllMocks() when appropriate) to ensure isolation
Do not rely on Nuxt auto-imports in tests; import required Vue utilities explicitly in test files
Remember that vi.mock calls are hoisted; avoid mixing mock declarations and module mocks incorrectly

Files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
**/__test__/components/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/components/**/*.ts: Component tests should use mount from @vue/test-utils
Stub complex child components that are not the focus of a component test
Mock external dependencies and services in component tests (e.g., vi.mock for helper modules)
Use createTestingPinia() to mock Pinia stores when testing Vue components
Test that expected elements are rendered and verify component output rather than internals
Test component interactions (clicks, inputs) using await element.trigger and setValue, and verify emitted events via wrapper.emitted()
Prefer semantic queries like find('button') or CSS selectors; avoid relying on data-test IDs unless necessary
Assert rendered text with wrapper.text(), check attributes via element.attributes(), and verify element existence with expect(element.exists()).toBe(true)
In component tests that use real store implementations, configure createTestingPinia with stubActions: false when you need real action behavior
Use findComponent(ComponentName) for locating child components and findAll for collections

Files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
web/__test__/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place Vue component tests under web/test; run with pnpm test

Files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
web/__test__/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.{test,spec}.{ts,tsx}: Use mount from Vue Test Utils for component testing
Stub complex child components that aren’t the focus of the test
Mock external dependencies and services in component tests
Test component behavior and output, not implementation details
Use createTestingPinia() for mocking stores in components
Prefer semantic queries like find('button') over data-test IDs
Use await nextTick() for DOM updates before assertions
For store tests, use createPinia() and setActivePinia
Only use createTestingPinia when its special features are needed
Let stores initialize with natural default state; don’t mock the store under test

Files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
🧠 Learnings (15)
📚 Learning: 2024-11-01T17:52:55.311Z
Learnt from: pujitm
PR: unraid/api#941
File: web/components/Notifications/Item.vue:0-0
Timestamp: 2024-11-01T17:52:55.311Z
Learning: In the `web/components/Notifications/Item.vue` component, ensure that when delete or archive mutations fail, the notification remains displayed, and an error message is shown below the action buttons to inform the user.

Applied to files:

  • web/src/components/Notifications/Item.vue
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/{store,stores}/**/*.ts : In store files, explicitly import Vue reactivity utilities (e.g., computed, ref, watchEffect); do not rely on Nuxt auto-imports

Applied to files:

  • web/src/components/Notifications/Item.vue
  • web/src/components/Notifications/Sidebar.vue
  • web/src/components/Notifications/List.vue
  • web/src/components/ConnectSettings/ConnectSettings.standalone.vue
  • web/src/components/Logs/LogViewer.standalone.vue
  • web/src/components/Logs/SingleLogViewer.vue
📚 Learning: 2025-03-12T13:35:43.900Z
Learnt from: pujitm
PR: unraid/api#1211
File: web/codegen.ts:14-14
Timestamp: 2025-03-12T13:35:43.900Z
Learning: The JSON scalar type in web/codegen.ts was temporarily changed from 'string' to 'any' for compatibility with JsonForms integration. This change facilitates the implementation of the Connect settings web component.

Applied to files:

  • api/src/unraid-api/types/json-forms.ts
📚 Learning: 2025-09-02T19:35:14.427Z
Learnt from: elibosley
PR: unraid/api#1639
File: web/components/standalone-mount.ts:38-54
Timestamp: 2025-09-02T19:35:14.427Z
Learning: In the unraid/api project, exposing Apollo client and GraphQL utilities (apolloClient, gql, graphqlParse) on the global window object is intentional to allow developers to interact with GraphQL from the browser console without needing to be inside Vue components. Developer experience is prioritized over minimizing global exposure in this context.

Applied to files:

  • web/README.md
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Always run pnpm codegen for GraphQL in the web directory

Applied to files:

  • api/package.json
  • web/package.json
📚 Learning: 2025-07-21T14:00:25.140Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Always run `pnpm codegen` for GraphQL code generation in the web directory

Applied to files:

  • api/package.json
  • web/package.json
📚 Learning: 2024-12-09T15:45:46.492Z
Learnt from: pujitm
PR: unraid/api#975
File: web/components/Notifications/TabList.vue:1-4
Timestamp: 2024-12-09T15:45:46.492Z
Learning: In our Nuxt.js setup for the `web` project, it's not necessary to explicitly import `computed` from `vue` in Vue components, as it's globally available.

Applied to files:

  • web/src/components/Logs/LogFilterInput.vue
📚 Learning: 2025-06-13T17:14:21.739Z
Learnt from: pujitm
PR: unraid/api#1417
File: web/components/ConnectSettings/ConnectSettings.ce.vue:11-18
Timestamp: 2025-06-13T17:14:21.739Z
Learning: The project’s build tooling auto-imports common Vue/Pinia helpers such as `storeToRefs`, so explicit import statements for them are not required.

Applied to files:

  • web/src/components/ConnectSettings/ConnectSettings.standalone.vue
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Component tests should use mount from vue/test-utils

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in component tests (e.g., vi.mock for helper modules)

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Use factory functions in vi.mock calls to define mocks and avoid hoisting pitfalls

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Place module mock declarations (vi.mock) at the top level of the test file to avoid hoisting issues

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Remember that vi.mock calls are hoisted; avoid mixing mock declarations and module mocks incorrectly

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Use createTestingPinia() to mock Pinia stores when testing Vue components

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Reset/clear mocks between tests using vi.clearAllMocks() (and vi.resetAllMocks() when appropriate) to ensure isolation

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
🧬 Code graph analysis (7)
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers2.php (1)
web/scripts/extract-translations.mjs (1)
  • locale (97-97)
web/src/helpers/i18n-loader.ts (1)
web/scripts/extract-translations.mjs (3)
  • rawLocale (96-96)
  • rawLocale (129-129)
  • locale (97-97)
web/scripts/extract-translations.mjs (1)
api/scripts/extract-translations.mjs (9)
  • raw (90-90)
  • ignorePatterns (10-17)
  • files (133-137)
  • keys (54-54)
  • keys (145-145)
  • content (143-143)
  • sourceFile (144-144)
  • added (108-108)
  • added (150-150)
api/scripts/extract-translations.mjs (1)
web/scripts/extract-translations.mjs (8)
  • ignorePatterns (32-39)
  • sourceFile (58-58)
  • keys (53-53)
  • visit (60-68)
  • raw (21-21)
  • added (188-188)
  • files (41-41)
  • content (57-57)
web/__test__/components/Wrapper/mount-engine.test.ts (1)
web/src/components/Wrapper/mount-engine.ts (1)
  • mountUnifiedApp (80-198)
web/src/components/Wrapper/mount-engine.ts (1)
web/src/helpers/i18n-loader.ts (3)
  • createI18nInstance (59-69)
  • ensureLocale (93-115)
  • getWindowLocale (117-122)
api/src/unraid-api/graph/resolvers/sso/core/oidc-config.service.ts (1)
api/src/unraid-api/graph/utils/form-utils.ts (1)
  • createSimpleLabeledControl (7-55)
🪛 GitHub Actions: CI - Main (API)
package.json

[error] 1-1: Command failed with exit code 1.

api/src/unraid-api/graph/resolvers/api-key/api-key-form.service.ts

[error] 152-152: Type '{ type: string; properties: { resources: { type: string; i18n: "jsonforms.apiKey.customPermissions.resources"; title: string; items: { type: string; enum: Resource[]; }; uniqueItems: true; minItems: number; default: Resource[]; }; actions: { ...; }; }; required: string[]; }' is not assignable to type 'JsonSchema4 | JsonSchema7 | JsonSchema4[] | JsonSchema7[] | undefined'.

api/package.json

[error] 1-1: Command failed with exit code 1.

web/__test__/components/Wrapper/mount-engine.test.ts

[error] 59-59: pnpm run type-check failed. TypeScript error TS2558: Expected 0-1 type arguments, but got 2. (test/components/Wrapper/mount-engine.test.ts:59:35)

web/package.json

[error] 1-1: Command failed with exit code 1.

<DispatchRenderer
:schema="layout.schema"
:uischema="element as UISchemaElement"
:uischema="element as unknown as UISchemaElement"
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

🧩 Analysis chain

Verify the double cast necessity.

The cast element as unknown as UISchemaElement bypasses TypeScript's type checking and suggests that element may not fully satisfy UISchemaElement's requirements. This could hide runtime issues if required properties are missing.

Consider:

  1. Checking if element should be typed as UISchemaElement from the start (e.g., by constraining the elements array type)
  2. Verifying that all elements in the filtered array actually have the properties DispatchRenderer expects
  3. If the mismatch is deliberate (e.g., using a subset of UISchemaElement), documenting why the double cast is safe

Run the following script to verify how elements are typed and whether they consistently match UISchemaElement:


🏁 Script executed:

#!/bin/bash
# Description: Check the types of elements used in AccordionLayout and related components

# Search for UISchemaElement and BaseUISchemaElement usage patterns
ast-grep --pattern $'interface BaseUISchemaElement {
  $$$
}'

# Search for where elements are defined/constrained
rg -nP --type=ts 'elements\s*:\s*\w+\[\]' unraid-ui/src/forms/

# Search for other uses of similar double casts in the codebase
rg -nP 'as unknown as UISchemaElement' unraid-ui/

Length of output: 106


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' unraid-ui/src/forms/AccordionLayout.vue

Length of output: 5644


Remove the unnecessary double cast
Strongly type the computed elements as UISchemaElement[] (e.g. const elements = computed<UISchemaElement[]>(() => { … })) so that in the template you can use

:uischema="element"

directly without as unknown as UISchemaElement.

🤖 Prompt for AI Agents
In unraid-ui/src/forms/AccordionLayout.vue around line 24, the template uses an
unnecessary double cast ":uischema=\"element as unknown as UISchemaElement\"";
strongly type the computed elements as UISchemaElement[] (e.g., define the
computed with computed<UISchemaElement[]>() in the script setup or component
code and ensure UISchemaElement is imported/available) so each item is already
typed and then replace the template binding with the direct
":uischema=\"element\"" and remove the double cast.

Comment on lines +31 to +39
const apiSourceRoot = path.resolve(process.cwd(), '../api/src');
const ignorePatterns = [
'**/__tests__/**',
'**/__test__/**',
'**/*.spec.ts',
'**/*.spec.js',
'**/*.test.ts',
'**/*.test.js',
];
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

🧩 Analysis chain

Verify path assumption for ../api/src

The script assumes it’s run with CWD at web/, resolving ../api/src. This may break under different runners.

Run this from the repo root to confirm expected layout and that locales exist:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[1] CWD:"
pwd

echo "[2] Check web locales dir:"
fd -t d -a '^locales$' web/src || true
fd -t f -a '\.json$' web/src/locales || true

echo "[3] Check api src dir as resolved by script:"
if [ -d "web" ]; then
  (cd web && node -e "console.log(require('node:path').resolve(process.cwd(), '../api/src'))")
fi
fd -t d -a '^src$' api || true
fd -t f -a '\.ts$' api/src | head -n 5

Length of output: 1648


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "[4] Resolve api path from repo root:"
node -e "console.log(require('node:path').resolve(process.cwd(), '../api/src'))"
if [ -d "$(node -e "console.log(require('node:path').resolve(process.cwd(), '../api/src'))")" ]; then
  echo "Exists"
else
  echo "Does not exist"
fi

Length of output: 74


Resolve apiSourceRoot relative to script file, not CWD
path.resolve(process.cwd(), '../api/src') breaks if the script is run from the repo root. Replace it with resolution based on the script’s directory, for example:

import { fileURLToPath } from 'url';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const apiSourceRoot = path.resolve(__dirname, '../../api/src');

This ensures the correct path regardless of working directory.

🤖 Prompt for AI Agents
In web/scripts/extract-translations.mjs around lines 31 to 39, apiSourceRoot is
being resolved from process.cwd(), which breaks when the script is invoked from
different working directories; change resolution to be based on the script file
location by deriving __dirname from import.meta.url (using fileURLToPath and
path.dirname) and then resolve apiSourceRoot relative to that directory (e.g.,
path.resolve(__dirname, '../../api/src')) so the path is correct regardless of
CWD.

Comment on lines +168 to 175
<Label for="log-file-select">{{ t('logs.viewer.logFileLabel') }}</Label>
<Select
v-model="selectedLogFile"
:items="logFileOptions"
placeholder="Select a log file"
:placeholder="t('logs.viewer.selectLogFilePlaceholder')"
class="w-full"
/>
</div>
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 | 🟠 Major

Fix label-to-control association for Selects (add ids).

Labels use for="log-file-select" and for="highlight-language" but the Selects lack matching id attributes. Screen readers won’t associate them.

Apply:

-          <Select
+          <Select id="log-file-select"
             v-model="selectedLogFile"
             :items="logFileOptions"
             :placeholder="t('logs.viewer.selectLogFilePlaceholder')"
             class="w-full"
           />
-          <Select
+          <Select id="highlight-language"
             v-model="highlightLanguage"
             :items="highlightLanguageOptions"
             :placeholder="t('logs.viewer.selectLanguagePlaceholder')"
             class="w-full"
           />

Also applies to: 189-197

🤖 Prompt for AI Agents
In web/src/components/Logs/LogViewer.standalone.vue around lines 168-175 and
189-197 the Label elements use for="log-file-select" and
for="highlight-language" but the corresponding Select components lack matching
id attributes; add id="log-file-select" to the Select bound to selectedLogFile
and add id="highlight-language" to the Select (or component) for highlight
language so the label for attributes correctly associate with their controls for
accessibility; ensure the id values exactly match the Label for values and keep
them unique within the page.

<div
:class="cn('fixed inset-0 z-0 transition-opacity', overlayColor, overlayOpacity)"
:title="showCloseX ? t('Click to close modal') : undefined"
:title="showCloseX ? t('modal.clickToCloseModal') : undefined"
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

❓ Verification inconclusive

Verify the i18n key exists in the base locale.

The key modal.clickToCloseModal follows good namespacing practices. Ensure it exists in web/src/locales/en.json to avoid runtime translation failures.

Run the following script to verify the key exists:


🏁 Script executed:

#!/bin/bash
# Description: Verify modal.clickToCloseModal exists in en.json

rg -n '"modal"\s*:' web/src/locales/en.json -A 10 | rg 'clickToCloseModal'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Broad search for the i18n key in en.json
rg -n '"clickToCloseModal"' web/src/locales/en.json

Length of output: 0


Add missing i18n key
The key modal.clickToCloseModal is not defined in web/src/locales/en.json; add it under the modal section (e.g., "clickToCloseModal": "Click to close modal").

🤖 Prompt for AI Agents
In web/src/components/Modal.vue around line 100, the template references the
i18n key modal.clickToCloseModal which is missing from the English locale; open
web/src/locales/en.json and add "clickToCloseModal": "Click to close modal" (or
the desired copy) under the existing "modal" object so the template can resolve
the translation.

Comment on lines +241 to 243
:text="t('updateOs.update.viewAvailableUpdates')"
:title="!acknowledgeBackup ? t('updateOs.update.acklowledgeThatYouHaveMadeA') : ''"
class="flex-none"
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 | 🟠 Major

Typo in translation key will break lookup

Key has a misspelling: acklowledge → acknowledge.

Apply this fix:

-          :title="!acknowledgeBackup ? t('updateOs.update.acklowledgeThatYouHaveMadeA') : ''"
+          :title="!acknowledgeBackup ? t('updateOs.update.acknowledgeThatYouHaveMadeA') : ''"
🤖 Prompt for AI Agents
In web/src/components/UpdateOs/Update.vue around lines 241 to 243 the
translation key used in the :title binding is misspelled as
"acklowledgeThatYouHaveMadeA" which will break lookup; change the key to the
correct spelling "acknowledgeThatYouHaveMadeA" (update the string in the :title
attribute and any other occurrences) so the i18n lookup resolves properly.

Comment on lines 41 to 45
if (availableWithRenewal.value) {
return props.t('Unraid OS {0} Released', [availableWithRenewal.value]);
return props.t('headerOsVersion.unraidOsReleased', [availableWithRenewal.value]);
}
return props.t('License Key Updates Expired');
return props.t('updateOs.updateIneligible.licenseKeyUpdatesExpired');
});
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

Incorrect interpolation value for heading; boolean passed to t().

availableWithRenewal.value is boolean. The heading should interpolate the release/version string from the update response.

Suggest using the version from updateOsResponse (or the release object) instead:

-  if (availableWithRenewal.value) {
-    return props.t('headerOsVersion.unraidOsReleased', [availableWithRenewal.value]);
-  }
+  if (availableWithRenewal.value) {
+    return props.t('headerOsVersion.unraidOsReleased', [updateOsResponse.value?.version ?? '']);
+  }
📝 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
if (availableWithRenewal.value) {
return props.t('Unraid OS {0} Released', [availableWithRenewal.value]);
return props.t('headerOsVersion.unraidOsReleased', [availableWithRenewal.value]);
}
return props.t('License Key Updates Expired');
return props.t('updateOs.updateIneligible.licenseKeyUpdatesExpired');
});
if (availableWithRenewal.value) {
return props.t(
'headerOsVersion.unraidOsReleased',
[updateOsResponse.value?.version ?? '']
);
}
return props.t('updateOs.updateIneligible.licenseKeyUpdatesExpired');
});
🤖 Prompt for AI Agents
web/src/components/UpdateOs/UpdateIneligible.vue lines 41-45: the code is
passing the boolean availableWithRenewal.value into the translation
interpolation, but the heading expects the release/version string from the
update response; replace the boolean with the actual version/release string from
updateOsResponse (or the release object) when calling props.t, and add a safe
null/undefined check (fall back to a sensible default string) so you pass a
string version like updateOsResponse.latestRelease.version (or the release.name)
rather than the boolean.

@@ -0,0 +1,124 @@
import { createI18n } from 'vue-i18n';

import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';
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

Use .js extension for local TS ESM import.

Align with ESM compatibility guidance.

[As per coding guidelines]

-import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';
+import { createHtmlEntityDecoder } from '~/helpers/i18n-utils.js';
📝 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
import { createHtmlEntityDecoder } from '~/helpers/i18n-utils';
import { createHtmlEntityDecoder } from '~/helpers/i18n-utils.js';
🤖 Prompt for AI Agents
In web/src/helpers/i18n-loader.ts at line 3, the local ESM import lacks a .js
extension which breaks TS ESM runtime resolution; update the import specifier to
include the .js extension (e.g. import from '~/helpers/i18n-utils.js') so the
runtime can resolve the module, and ensure the referenced file is emitted with a
.js extension by your build setup.

"registration.messages.yourLicenseKeySOsUpdate": "Din licensnøgles OS-opdateringsberettigelse er udløbet. Forny venligst din licensnøgle for at aktivere opdateringer udgivet efter din udløbsdato.",
"registration.messages.yourServerWillNotBeUsable": "<p>Din server vil ikke kunne bruges, før du køber en Registreringsnøgle eller installerer en gratis 30 dages <em>Prøvenøgle</em>. En <em>Prøvenøgle</em> giver alle funktionerne i en Unleashed Registreringsnøgle.</p><p>Registreringsnøgler er bundet til din USB Flash opstartsenheds serienummer (GUID). Brug venligst en højkvalitets enhedsmærke mindst 1GB i størrelse.</p><p>Bemærk: USB kortlæserne er generelt ikke understøttede, da de fleste ikke har unikke serienumre.</p><p><strong>Vigtigt:</strong></p><ul class='list-disc pl-16px'><li>Sørg for, at din servertid er korrekt afstemt inden for 5 minutter</li><li>Sørg for, at der er angivet en DNS-server</li></ul>",
"registration.messages.yourTrialKeyIncludesAllThe": "<p>Din <em>Prøvenøgle</em> inkluderer alle funktionerne og enhedsunderstøttelsen af en <em>Unleashed</em> nøgle.</p><p>Efter din <em>Prøveperiode</em> er udløbet, fungerer din server <strong>normalt</strong>, indtil næste gang du standser arrays eller genstarter din server.</p><p>På dette tidspunkt kan du enten købe en licensnøgle eller anmode om en <em>Prøve</em> forlængelse.</p>",
"registration.messages.yourTrialKeyRequiresAnInternet": "<p>Din Prøvenøgle kræver en internetforbindelse.</p><p><a href='/Settings/NetworkSettings class='underline'>Tjek venligst Indstillinger > Netværk</a></p>",
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

🧩 Analysis chain

Fix malformed HTML attributes (broken href/class quoting).

The anchor’s href/class quotes are misordered, which will break rendering and could affect link behavior.

Apply:

-"registration.messages.yourTrialKeyRequiresAnInternet": "<p>Din Prøvenøgle kræver en internetforbindelse.</p><p><a href='/Settings/NetworkSettings class='underline'>Tjek venligst Indstillinger > Netværk</a></p>",
+"registration.messages.yourTrialKeyRequiresAnInternet": "<p>Din Prøvenøgle kræver en internetforbindelse.</p><p><a href='/Settings/NetworkSettings' class='underline'>Tjek venligst Indstillinger > Netværk</a></p>",

Optionally, prefer double quotes for attributes to reduce escaping issues in JSON strings.

Also consider scanning for similar issues:


🏁 Script executed:

#!/bin/bash
# Search for likely malformed attribute quoting patterns in locale files
rg -nP "href=['\"][^\"']+\\s+class=['\"]" web/src/locales/*.json

Length of output: 5119


Fix malformed HTML attribute quoting across all locale JSON files.

In every web/src/locales/*.json, change:

- <a href='/Settings/NetworkSettings class='underline'>
+ <a href='/Settings/NetworkSettings' class='underline'>

Optionally use double quotes for both attributes to simplify JSON escaping.

📝 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
"registration.messages.yourTrialKeyRequiresAnInternet": "<p>Din Prøvenøgle kræver en internetforbindelse.</p><p><a href='/Settings/NetworkSettings class='underline'>Tjek venligst Indstillinger > Netværk</a></p>",
"registration.messages.yourTrialKeyRequiresAnInternet": "<p>Din Prøvenøgle kræver en internetforbindelse.</p><p><a href='/Settings/NetworkSettings' class='underline'>Tjek venligst Indstillinger > Netværk</a></p>",
🤖 Prompt for AI Agents
In web/src/locales/da.json around line 306 (and apply across all
web/src/locales/*.json files), the HTML attribute quoting in the string is
malformed (the href attribute and class attribute quotes are
mixed/unterminated). Fix each locale string by properly quoting HTML attributes
and escaping quotes for JSON: use double quotes for HTML attributes and escape
them in the JSON string (or use single quotes for HTML attributes consistently
and ensure the JSON string delimiters remain valid). Update all locale files to
the same convention so attributes like href and class are correctly quoted and
JSON remains valid.

"userProfile.callbackFeedback.thisDowngradeWillRequireAReboot": "Denne nedgradering kræver en genstart",
"userProfile.callbackFeedback.thisUpdateWillRequireAReboot": "Denne opdatering kræver en genstart",
"userProfile.callbackFeedback.unraid": "Unraid {0}",
"userProfile.callbackFeedback.updateUnraidOsConfirmationRequired": "Confirmation of Update Unraid OS required",
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 | 🟡 Minor

Untranslated entry remains in English.

"userProfile.callbackFeedback.updateUnraidOsConfirmationRequired" is still English. Not blocking, but worth translating for consistency.

🤖 Prompt for AI Agents
In web/src/locales/da.json around line 543 the value for
"userProfile.callbackFeedback.updateUnraidOsConfirmationRequired" is still in
English; replace it with a Danish translation such as "Bekræftelse kræves for
opdatering af Unraid OS" (or an equivalent Danish phrasing) so the locale file
is fully translated and consistent.

Copy link
Contributor

github-actions bot commented Oct 3, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

…ences

- Deleted the translations.php file from the dynamix.my.servers plugin, streamlining the localization process.
- Updated backup and exit scripts to remove references to the deleted translations.php file, ensuring consistency across the application.
- Enhanced test files to utilize a new test translation utility, improving internationalization support in various components.

These changes simplify the localization setup and improve the overall maintainability of the codebase.
Copy link
Contributor

github-actions bot commented Oct 3, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
web/src/locales/en.json (1)

304-306: HTML in translation strings may complicate maintenance and localization.

Several translation values contain raw HTML markup (e.g., <p>, <em>, <ul>, <li>, <a>, <strong>). This approach can make translations harder to manage and may pose risks if user-supplied data is ever interpolated into these strings.

Consider:

  • Moving HTML structure to component templates and using separate translation keys for text fragments.
  • Using markdown or a safe markup library if rich text is needed.
  • Ensuring that any dynamic content interpolated into these strings is properly sanitized.

Also applies to: 349-350, 356-356, 371-396

web/__test__/utils/i18n.ts (1)

15-41: Consider escaping special regex characters in placeholder names.

The replaceParams function constructs regular expressions from variable input (index at line 24, placeholder at line 31). While the risk is low in a test context with controlled locale keys, if placeholder names ever contain regex special characters (e.g., {foo.bar}, {test[0]}), this could cause regex errors or unexpected behavior.

To harden the implementation, escape special regex characters before constructing the RegExp:

+function escapeRegex(str: string | number): string {
+  return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+}
+
 function replaceParams(template: string, params?: unknown): string {
   if (params === undefined || params === null) {
     return template;
   }

   let result = template;

   if (Array.isArray(params)) {
     params.forEach((value, index) => {
-      result = result.replace(new RegExp(`\\{${index}\\}`, 'g'), String(value));
+      result = result.replace(new RegExp(`\\{${escapeRegex(index)}\\}`, 'g'), String(value));
     });
     return result;
   }

   if (typeof params === 'object') {
     Object.entries(params as AnyObject).forEach(([placeholder, value]) => {
-      result = result.replace(new RegExp(`\\{${placeholder}\\}`, 'g'), String(value));
+      result = result.replace(new RegExp(`\\{${escapeRegex(placeholder)}\\}`, 'g'), String(value));
     });
     return result;
   }

   if (typeof params === 'number' || typeof params === 'string' || typeof params === 'boolean') {
     return result.replace(/\{0\}/g, String(params));
   }

   return result;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7583331 and f48c85c.

📒 Files selected for processing (20)
  • api/src/i18n/bn.json (1 hunks)
  • api/src/i18n/hi.json (1 hunks)
  • api/src/i18n/hr.json (1 hunks)
  • api/src/i18n/lv.json (1 hunks)
  • api/src/i18n/uk.json (1 hunks)
  • api/src/i18n/zh.json (1 hunks)
  • plugin/plugins/dynamix.unraid.net.plg (0 hunks)
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php (0 hunks)
  • web/__test__/components/Activation/ActivationModal.test.ts (2 hunks)
  • web/__test__/components/Activation/WelcomeModal.test.ts (3 hunks)
  • web/__test__/components/CheckUpdateResponseModal.test.ts (2 hunks)
  • web/__test__/components/DowngradeOs.test.ts (6 hunks)
  • web/__test__/components/DownloadApiLogs.test.ts (7 hunks)
  • web/__test__/components/HeaderOsVersion.test.ts (4 hunks)
  • web/__test__/components/Logs/SingleLogViewer.test.ts (5 hunks)
  • web/__test__/components/Registration.test.ts (2 hunks)
  • web/__test__/components/SsoButton.test.ts (2 hunks)
  • web/__test__/components/UpdateOs.test.ts (3 hunks)
  • web/__test__/utils/i18n.ts (1 hunks)
  • web/src/locales/en.json (1 hunks)
💤 Files with no reviewable changes (2)
  • plugin/plugins/dynamix.unraid.net.plg
  • plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php
✅ Files skipped from review due to trivial changes (7)
  • api/src/i18n/uk.json
  • api/src/i18n/lv.json
  • api/src/i18n/zh.json
  • api/src/i18n/hi.json
  • api/src/i18n/hr.json
  • api/src/i18n/bn.json
  • web/test/components/Logs/SingleLogViewer.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
{**/*.test.ts,**/__test__/{components,store}/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

{**/*.test.ts,**/__test__/{components,store}/**/*.ts}: Use .rejects.toThrow() without arguments when asserting that async functions throw; avoid checking exact error message strings unless the message format is explicitly under test
Focus tests on observable behavior and outcomes, not implementation details such as exact error messages
Use await nextTick() for DOM update assertions and flushPromises() for complex async chains; always await async operations before asserting
Place module mock declarations (vi.mock) at the top level of the test file to avoid hoisting issues
Use factory functions in vi.mock calls to define mocks and avoid hoisting pitfalls
Use vi.spyOn() to specify return values or behavior of methods under test
Reset/clear mocks between tests using vi.clearAllMocks() (and vi.resetAllMocks() when appropriate) to ensure isolation
Do not rely on Nuxt auto-imports in tests; import required Vue utilities explicitly in test files
Remember that vi.mock calls are hoisted; avoid mixing mock declarations and module mocks incorrectly

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/WelcomeModal.test.ts
  • web/__test__/components/Activation/ActivationModal.test.ts
  • web/__test__/components/CheckUpdateResponseModal.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/components/HeaderOsVersion.test.ts
**/__test__/components/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/components/**/*.ts: Component tests should use mount from @vue/test-utils
Stub complex child components that are not the focus of a component test
Mock external dependencies and services in component tests (e.g., vi.mock for helper modules)
Use createTestingPinia() to mock Pinia stores when testing Vue components
Test that expected elements are rendered and verify component output rather than internals
Test component interactions (clicks, inputs) using await element.trigger and setValue, and verify emitted events via wrapper.emitted()
Prefer semantic queries like find('button') or CSS selectors; avoid relying on data-test IDs unless necessary
Assert rendered text with wrapper.text(), check attributes via element.attributes(), and verify element existence with expect(element.exists()).toBe(true)
In component tests that use real store implementations, configure createTestingPinia with stubActions: false when you need real action behavior
Use findComponent(ComponentName) for locating child components and findAll for collections

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/WelcomeModal.test.ts
  • web/__test__/components/Activation/ActivationModal.test.ts
  • web/__test__/components/CheckUpdateResponseModal.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/components/HeaderOsVersion.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/WelcomeModal.test.ts
  • web/__test__/components/Activation/ActivationModal.test.ts
  • web/__test__/components/CheckUpdateResponseModal.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/utils/i18n.ts
  • web/__test__/components/HeaderOsVersion.test.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/WelcomeModal.test.ts
  • web/__test__/components/Activation/ActivationModal.test.ts
  • web/__test__/components/CheckUpdateResponseModal.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/components/HeaderOsVersion.test.ts
web/__test__/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place Vue component tests under web/test; run with pnpm test

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/WelcomeModal.test.ts
  • web/__test__/components/Activation/ActivationModal.test.ts
  • web/__test__/components/CheckUpdateResponseModal.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/utils/i18n.ts
  • web/__test__/components/HeaderOsVersion.test.ts
web/__test__/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.{test,spec}.{ts,tsx}: Use mount from Vue Test Utils for component testing
Stub complex child components that aren’t the focus of the test
Mock external dependencies and services in component tests
Test component behavior and output, not implementation details
Use createTestingPinia() for mocking stores in components
Prefer semantic queries like find('button') over data-test IDs
Use await nextTick() for DOM updates before assertions
For store tests, use createPinia() and setActivePinia
Only use createTestingPinia when its special features are needed
Let stores initialize with natural default state; don’t mock the store under test

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/WelcomeModal.test.ts
  • web/__test__/components/Activation/ActivationModal.test.ts
  • web/__test__/components/CheckUpdateResponseModal.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/components/HeaderOsVersion.test.ts
🧠 Learnings (14)
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Do not rely on Nuxt auto-imports in tests; import required Vue utilities explicitly in test files

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/HeaderOsVersion.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Use factory functions in vi.mock calls to define mocks and avoid hoisting pitfalls

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/components/HeaderOsVersion.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/store/**/*.ts : In store tests, mock external dependencies used by the store and verify interactions with those mocks

Applied to files:

  • web/__test__/components/Registration.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in component tests (e.g., vi.mock for helper modules)

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
  • web/__test__/components/HeaderOsVersion.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Assert rendered text with wrapper.text(), check attributes via element.attributes(), and verify element existence with expect(element.exists()).toBe(true)

Applied to files:

  • web/__test__/components/Activation/WelcomeModal.test.ts
  • web/__test__/components/DownloadApiLogs.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Component tests should use mount from vue/test-utils

Applied to files:

  • web/__test__/components/Activation/ActivationModal.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/UpdateOs.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Place module mock declarations (vi.mock) at the top level of the test file to avoid hoisting issues

Applied to files:

  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/DowngradeOs.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Remember that vi.mock calls are hoisted; avoid mixing mock declarations and module mocks incorrectly

Applied to files:

  • web/__test__/components/SsoButton.test.ts
  • web/__test__/components/UpdateOs.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Use createTestingPinia() to mock Pinia stores when testing Vue components

Applied to files:

  • web/__test__/components/DowngradeOs.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: This Nuxt app is tested with vitest outside the Nuxt runtime; structure tests accordingly

Applied to files:

  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : Use mount from Vue Test Utils for component testing

Applied to files:

  • web/__test__/components/DownloadApiLogs.test.ts
  • web/__test__/components/UpdateOs.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to api/**/*.{test,spec}.{ts,tsx} : API test suite is Vitest; do not use Jest

Applied to files:

  • web/__test__/components/DownloadApiLogs.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to {api,web}/**/*.{test,spec}.{ts,tsx} : Use mocks as nouns, not verbs

Applied to files:

  • web/__test__/components/DownloadApiLogs.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : Mock external dependencies and services in component tests

Applied to files:

  • web/__test__/components/DownloadApiLogs.test.ts
🧬 Code graph analysis (10)
web/__test__/components/Registration.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • testTranslate (43-47)
web/__test__/components/Activation/WelcomeModal.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • testTranslate (43-47)
web/__test__/components/Activation/ActivationModal.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • testTranslate (43-47)
web/__test__/components/CheckUpdateResponseModal.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • testTranslate (43-47)
web/__test__/components/SsoButton.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • testTranslate (43-47)
web/__test__/components/DowngradeOs.test.ts (1)
web/__test__/utils/i18n.ts (2)
  • testTranslate (43-47)
  • createTestI18n (49-58)
web/__test__/components/DownloadApiLogs.test.ts (1)
web/__test__/utils/i18n.ts (2)
  • testTranslate (43-47)
  • createTestI18n (49-58)
web/__test__/components/UpdateOs.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • testTranslate (43-47)
web/__test__/utils/i18n.ts (1)
web/src/helpers/i18n-loader.ts (1)
  • DEFAULT_LOCALE (124-124)
web/__test__/components/HeaderOsVersion.test.ts (1)
web/__test__/utils/i18n.ts (2)
  • testTranslate (43-47)
  • createTestI18n (49-58)
🪛 ast-grep (0.39.5)
web/__test__/utils/i18n.ts

[warning] 23-23: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\{${index}\\}, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 30-30: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(\\{${placeholder}\\}, 'g')
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (12)
web/__test__/components/Activation/ActivationModal.test.ts (1)

13-13: LGTM! Correct adoption of centralized i18n test utilities.

The import of testTranslate and its assignment to mockT correctly replaces the local translation mock with the centralized test utility, aligning with the PR's i18n testing strategy.

Also applies to: 40-40

web/__test__/components/UpdateOs.test.ts (1)

12-12: LGTM! Proper spy wrapper for testTranslate.

The pattern of wrapping testTranslate in vi.fn() allows the test to track translation calls while using the real implementation from the centralized utilities. The beforeEach correctly resets the mock to ensure test isolation.

Also applies to: 36-36, 71-71

web/__test__/components/Registration.test.ts (1)

19-19: LGTM! Clean async mock pattern.

The async mock pattern preserves the original vue-i18n module while overriding only useI18n to return testTranslate. This is a cleaner approach than fully replacing the module and aligns with the PR's centralized i18n testing strategy.

Also applies to: 116-124

web/__test__/components/SsoButton.test.ts (1)

13-13: LGTM! Straightforward migration to centralized test utilities.

The import and usage of testTranslate correctly replaces the local translation mock with the centralized test utility.

Also applies to: 33-36

web/__test__/components/HeaderOsVersion.test.ts (1)

20-20: LGTM! Complete i18n test setup.

The test correctly imports and uses both testTranslate and createTestI18n, providing a complete i18n context for the component. The async mock pattern preserves the original vue-i18n module while overriding the translation function, and the i18n plugin is consistently added to all mount operations.

Also applies to: 64-72, 101-101, 156-156

web/__test__/components/DowngradeOs.test.ts (1)

13-13: LGTM! Consistent i18n setup across all tests.

The test correctly imports and applies both createTestI18n and testTranslate, ensuring consistent i18n context across all mounting operations. The async mock pattern preserves the original module while providing the test translation function.

Also applies to: 34-42, 79-79, 96-96, 124-124, 150-150

web/__test__/components/CheckUpdateResponseModal.test.ts (1)

9-9: LGTM! Correct type casting for prop-based i18n.

The import of testTranslate and type cast to ComposerTranslation correctly adapts the test utility for use as a component prop, aligning with the component's prop-based i18n pattern.

Also applies to: 11-11

web/__test__/components/DownloadApiLogs.test.ts (1)

12-12: LGTM! Complete i18n test migration with assertion improvements.

The test correctly adopts both createTestI18n and testTranslate, providing a complete i18n context. The use of testTranslate() in assertions (lines 62, 80, 83, 86, 107-109) is particularly good practice, as it verifies that translations are correctly applied while making tests resilient to translation text changes.

Also applies to: 21-29, 41-41, 62-62, 68-68, 97-97

web/__test__/components/Activation/WelcomeModal.test.ts (2)

13-13: LGTM! Test updated correctly for i18n.

The test now uses testTranslate from the shared i18n test utilities, which provides real translation lookups against en.json. The switch from inline mock to testTranslate ensures tests validate actual translation keys and their interpolation logic.

Also applies to: 40-40, 119-128


130-146: LGTM! Title assertions correctly validate translated output.

Tests now assert against the actual translated strings returned by testTranslate, including placeholder replacement for the partner name scenario. This approach ensures that translation keys remain valid and that parameter interpolation works as expected.

web/__test__/utils/i18n.ts (2)

43-47: LGTM! testTranslate provides a practical test helper.

The testTranslate function effectively mimics the vue-i18n ComposerTranslation signature for test purposes. The type cast is acceptable here as it's a test utility that matches the expected interface.


49-58: LGTM! createTestI18n correctly initializes a test i18n instance.

The factory function creates a properly configured vue-i18n instance with:

  • Composition API mode (legacy: false)
  • Consistent locale and fallback (en_US)
  • Real locale messages from en.json

This allows tests to use a real i18n setup rather than mocks.

"userProfile.serverState.purchaseKey": "Purchase Key",
"userProfile.serverState.upgrade": "Upgrade",
"userProfile.serverState.upgradeKey": "Upgrade Key",
"userProfile.trial.errorCreatiingATrialKeyPlease": "Error creatiing a trial key. Please try again later.",
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 | 🟡 Minor

Typo in translation key.

Line 579 contains a typo: "userProfile.trial.errorCreatiingATrialKeyPlease" should be "errorCreatingATrialKeyPlease" (double 'i' in "Creatiing").

Apply this diff to fix the typo:

-  "userProfile.trial.errorCreatiingATrialKeyPlease": "Error creatiing a trial key. Please try again later.",
+  "userProfile.trial.errorCreatingATrialKeyPlease": "Error creating a trial key. Please try again later.",
📝 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
"userProfile.trial.errorCreatiingATrialKeyPlease": "Error creatiing a trial key. Please try again later.",
"userProfile.trial.errorCreatingATrialKeyPlease": "Error creating a trial key. Please try again later.",
🤖 Prompt for AI Agents
In web/src/locales/en.json around line 579 the translation key has a typo:
"userProfile.trial.errorCreatiingATrialKeyPlease" (double 'i' in "Creatiing");
update the key to "userProfile.trial.errorCreatingATrialKeyPlease" preserving
the existing value string exactly and ensure no other occurrences (search for
the old misspelled key and replace them if present) so references continue to
work.

…ation

- Introduced `i18n` property to JSON Schema 4 and 7 interfaces, enhancing localization capabilities for schemas.
- Updated OIDC configuration to correct `i18n` references for allowed domains, emails, and user IDs, ensuring accurate localization.
- Added `i18n` support in accordion layout configuration, improving internationalization for UI components.

These changes enhance the internationalization framework, making it more robust and user-friendly across the application.
- Updated JSON schema interfaces to adjust `i18n` properties, ensuring correct localization for both JSON Schema 4 and 7.
- Corrected `i18n` references in OIDC configuration for allowed domains, emails, and user IDs, enhancing localization accuracy.
- Added `i18n` support to accordion layout configuration, improving internationalization for UI components.

These changes enhance the internationalization framework, ensuring a more consistent and user-friendly experience across the application.
Copy link
Contributor

github-actions bot commented Oct 3, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

- Updated the mock function for retrieving the window locale to ensure proper type inference.
- Adjusted the infinite scroll directive in the Notifications List component for improved functionality.

These changes enhance the test reliability and component behavior, contributing to a more robust application.
Copy link
Contributor

github-actions bot commented Oct 3, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link

codecov bot commented Oct 3, 2025

Codecov Report

❌ Patch coverage is 23.66197% with 1084 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.57%. Comparing base (ed594e9) to head (f25a468).

Files with missing lines Patch % Lines
web/scripts/extract-translations.mjs 0.00% 466 Missing and 1 partial ⚠️
web/src/helpers/i18n-loader.ts 0.00% 93 Missing and 1 partial ⚠️
web/src/components/Logs/LogViewer.standalone.vue 0.00% 64 Missing ⚠️
...eb/src/components/UserProfile/CallbackFeedback.vue 0.00% 62 Missing ⚠️
...pi/graph/resolvers/sso/core/oidc-config.service.ts 41.66% 56 Missing ⚠️
web/scripts/sort-translations.mjs 0.00% 50 Missing and 1 partial ⚠️
web/src/components/Logs/LogFilterInput.vue 0.00% 26 Missing ⚠️
web/src/helpers/jsonforms-i18n.ts 0.00% 25 Missing and 1 partial ⚠️
api/src/unraid-api/graph/utils/form-utils.ts 36.84% 24 Missing ⚠️
unraid-ui/src/components/brand/BrandButton.vue 0.00% 24 Missing ⚠️
... and 24 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1739      +/-   ##
==========================================
- Coverage   53.35%   52.57%   -0.78%     
==========================================
  Files         860      865       +5     
  Lines       48275    49198     +923     
  Branches     4929     4940      +11     
==========================================
+ Hits        25756    25868     +112     
- Misses      22450    23257     +807     
- Partials       69       73       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

github-actions bot commented Oct 3, 2025

This plugin has been deployed to Cloudflare R2 and is available for testing.
Download it at this URL:

https://preview.dl.unraid.net/unraid-api/tag/PR1739/dynamix.unraid.net.plg

- Updated the i18n key assignment in createSimpleLabeledControl and createLabeledControl functions to directly use the provided i18nKey without appending '.label'.
- This change streamlines the localization process for form controls, ensuring more accurate internationalization.

These modifications enhance the clarity and functionality of the i18n integration in form utilities.
- Added support for the `@vue/compiler-sfc` version 3.5.20 in `package.json` and updated related dependencies in `pnpm-lock.yaml`.
- Refactored translation keys in `ActivationSteps.vue` to utilize the `useI18n` composable for dynamic localization.
- Enhanced the `extract-translations.mjs` script to improve the collection of translation keys, including new functions for handling JSON forms and TypeScript translation keys.
- Updated English locale file with new translation keys and refined existing ones for better clarity and consistency.

These changes significantly improve the internationalization framework, ensuring a more robust and user-friendly experience across the application.
- Updated test files across various components to utilize the `createTestI18n` utility for improved internationalization.
- Refactored translation handling in `Auth.test.ts`, `KeyActions.test.ts`, `Registration.test.ts`, and others to streamline localization processes.
- Removed direct translation key assignments in favor of using the `useI18n` composable, ensuring consistency and clarity in translation usage.

These changes improve the robustness of the i18n framework in tests, facilitating better localization practices across the application.
}));

const t = (key: string) => `translated_${key}`;
const t = (key: string) => `${key}`;

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused variable t.

Copilot Autofix

AI about 14 hours ago

To fix this issue, simply remove the declaration of the unused variable t from line 30. This means deleting the line:

const t = (key: string) => `${key}`;

No other code needs to be changed. There are no imports or dependencies required. This edit should be performed in web/test/components/KeyActions.test.ts, at line 30 (as shown).

Suggested changeset 1
web/__test__/components/KeyActions.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/__test__/components/KeyActions.test.ts b/web/__test__/components/KeyActions.test.ts
--- a/web/__test__/components/KeyActions.test.ts
+++ b/web/__test__/components/KeyActions.test.ts
@@ -27,7 +27,6 @@
   })),
 }));
 
-const t = (key: string) => `${key}`;
 
 describe('KeyActions', () => {
   beforeEach(() => {
EOF
@@ -27,7 +27,6 @@
})),
}));

const t = (key: string) => `${key}`;

describe('KeyActions', () => {
beforeEach(() => {
Copilot is powered by AI and may make mistakes. Always verify output.
import type { Mock, MockInstance } from 'vitest';

import SsoButtons from '~/components/sso/SsoButtons.vue';
import { createTestI18n, testTranslate } from '../utils/i18n';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import testTranslate.

Copilot Autofix

AI about 14 hours ago

To fix the issue, the unused import testTranslate should be removed from the import statement on line 13 in the file web/__test__/components/SsoButton.test.ts. The rest of the code should be left unchanged, and only the unused named import should be removed.


Suggested changeset 1
web/__test__/components/SsoButton.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/__test__/components/SsoButton.test.ts b/web/__test__/components/SsoButton.test.ts
--- a/web/__test__/components/SsoButton.test.ts
+++ b/web/__test__/components/SsoButton.test.ts
@@ -10,7 +10,7 @@
 import type { Mock, MockInstance } from 'vitest';
 
 import SsoButtons from '~/components/sso/SsoButtons.vue';
-import { createTestI18n, testTranslate } from '../utils/i18n';
+import { createTestI18n } from '../utils/i18n';
 
 // Mock the child components
 const SsoProviderButtonStub = {
EOF
@@ -10,7 +10,7 @@
import type { Mock, MockInstance } from 'vitest';

import SsoButtons from '~/components/sso/SsoButtons.vue';
import { createTestI18n, testTranslate } from '../utils/i18n';
import { createTestI18n } from '../utils/i18n';

// Mock the child components
const SsoProviderButtonStub = {
Copilot is powered by AI and may make mistakes. Always verify output.

import { WebguiState } from '~/composables/services/webgui';
import { useServerStore } from '~/store/server';
import { createTestI18n, testTranslate } from '../utils/i18n';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import createTestI18n.

Copilot Autofix

AI about 14 hours ago

To fix the problem, the unused imported identifier createTestI18n should be removed from the import statement. This involves editing the line:

import { createTestI18n, testTranslate } from '../utils/i18n';

to import only the actually used testTranslate:

import { testTranslate } from '../utils/i18n';

This change should only be made to the one relevant line (line 22), and no other changes or additions are necessary.


Suggested changeset 1
web/__test__/store/server.test.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/web/__test__/store/server.test.ts b/web/__test__/store/server.test.ts
--- a/web/__test__/store/server.test.ts
+++ b/web/__test__/store/server.test.ts
@@ -19,7 +19,7 @@
 
 import { WebguiState } from '~/composables/services/webgui';
 import { useServerStore } from '~/store/server';
-import { createTestI18n, testTranslate } from '../utils/i18n';
+import { testTranslate } from '../utils/i18n';
 
 // Mock vue-i18n for store tests
 vi.mock('vue-i18n', () => ({
EOF
@@ -19,7 +19,7 @@

import { WebguiState } from '~/composables/services/webgui';
import { useServerStore } from '~/store/server';
import { createTestI18n, testTranslate } from '../utils/i18n';
import { testTranslate } from '../utils/i18n';

// Mock vue-i18n for store tests
vi.mock('vue-i18n', () => ({
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

github-actions bot commented Oct 3, 2025

🚀 Storybook has been deployed to staging: https://unraid-ui-storybook-staging.unraid-workers.workers.dev

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (2)
web/__test__/components/Wrapper/mount-engine.test.ts (1)

49-65: Verified: TS2558 fix applied.

The previous TS2558 error has been resolved by using the correct Vitest function type annotation vi.fn<() => string | undefined>() at line 59.

web/scripts/extract-translations.mjs (1)

263-263: Path resolution still needs fixing.

As flagged in a previous review, apiSourceRoot at line 263 is resolved relative to process.cwd(), which breaks when the script is invoked from different working directories.

Resolve the path relative to the script file location instead:

+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
 async function collectJsonFormsKeys() {
-  const apiSourceRoot = path.resolve(process.cwd(), '../api/src');
+  const apiSourceRoot = path.resolve(__dirname, '../../api/src');
   const ignorePatterns = [
🧹 Nitpick comments (2)
web/__test__/components/Brand/Avatar.test.ts (1)

22-31: Clarify vue-i18n mock comment
Update the comment to reflect that this mock is for stores used in this component test, for example:

- // Mock vue-i18n for store tests
+ // Mock vue-i18n for stores used in component tests

This mock remains necessary because serverStore getters call useI18n.

web/__test__/components/SsoButton.test.ts (1)

13-13: Remove unused import.

testTranslate is imported but never used in this test file. Only createTestI18n is needed for the i18n plugin injection.

Apply this diff:

-import { createTestI18n, testTranslate } from '../utils/i18n';
+import { createTestI18n } from '../utils/i18n';
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f48c85c and f25a468.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • api/.eslintrc.ts (1 hunks)
  • api/src/types/jsonforms-i18n.d.ts (1 hunks)
  • api/src/unraid-api/graph/resolvers/sso/core/oidc-config.service.ts (32 hunks)
  • api/src/unraid-api/graph/utils/form-utils.ts (4 hunks)
  • web/__test__/components/Activation/ActivationModal.test.ts (3 hunks)
  • web/__test__/components/Activation/ActivationSteps.test.ts (2 hunks)
  • web/__test__/components/Auth.test.ts (5 hunks)
  • web/__test__/components/Brand/Avatar.test.ts (6 hunks)
  • web/__test__/components/KeyActions.test.ts (9 hunks)
  • web/__test__/components/Modals.test.ts (0 hunks)
  • web/__test__/components/Registration.test.ts (4 hunks)
  • web/__test__/components/SsoButton.test.ts (10 hunks)
  • web/__test__/components/UpdateOs.test.ts (9 hunks)
  • web/__test__/components/Wrapper/mount-engine.test.ts (3 hunks)
  • web/__test__/store/server.test.ts (1 hunks)
  • web/package.json (5 hunks)
  • web/scripts/extract-translations.mjs (1 hunks)
  • web/src/components/Activation/ActivationModal.vue (5 hunks)
  • web/src/components/Activation/ActivationSteps.vue (4 hunks)
  • web/src/components/KeyActions.vue (1 hunks)
  • web/src/components/Modals.standalone.vue (1 hunks)
  • web/src/components/Notifications/List.vue (4 hunks)
  • web/src/locales/en.json (1 hunks)
💤 Files with no reviewable changes (1)
  • web/test/components/Modals.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • web/src/components/Notifications/List.vue
  • web/test/components/UpdateOs.test.ts
  • web/test/components/Activation/ActivationModal.test.ts
  • api/src/unraid-api/graph/resolvers/sso/core/oidc-config.service.ts
  • web/src/locales/en.json
🧰 Additional context used
📓 Path-based instructions (11)
{**/*.test.ts,**/__test__/{components,store}/**/*.ts}

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

{**/*.test.ts,**/__test__/{components,store}/**/*.ts}: Use .rejects.toThrow() without arguments when asserting that async functions throw; avoid checking exact error message strings unless the message format is explicitly under test
Focus tests on observable behavior and outcomes, not implementation details such as exact error messages
Use await nextTick() for DOM update assertions and flushPromises() for complex async chains; always await async operations before asserting
Place module mock declarations (vi.mock) at the top level of the test file to avoid hoisting issues
Use factory functions in vi.mock calls to define mocks and avoid hoisting pitfalls
Use vi.spyOn() to specify return values or behavior of methods under test
Reset/clear mocks between tests using vi.clearAllMocks() (and vi.resetAllMocks() when appropriate) to ensure isolation
Do not rely on Nuxt auto-imports in tests; import required Vue utilities explicitly in test files
Remember that vi.mock calls are hoisted; avoid mixing mock declarations and module mocks incorrectly

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/ActivationSteps.test.ts
  • web/__test__/components/KeyActions.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/server.test.ts
**/__test__/components/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/components/**/*.ts: Component tests should use mount from @vue/test-utils
Stub complex child components that are not the focus of a component test
Mock external dependencies and services in component tests (e.g., vi.mock for helper modules)
Use createTestingPinia() to mock Pinia stores when testing Vue components
Test that expected elements are rendered and verify component output rather than internals
Test component interactions (clicks, inputs) using await element.trigger and setValue, and verify emitted events via wrapper.emitted()
Prefer semantic queries like find('button') or CSS selectors; avoid relying on data-test IDs unless necessary
Assert rendered text with wrapper.text(), check attributes via element.attributes(), and verify element existence with expect(element.exists()).toBe(true)
In component tests that use real store implementations, configure createTestingPinia with stubActions: false when you need real action behavior
Use findComponent(ComponentName) for locating child components and findAll for collections

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/ActivationSteps.test.ts
  • web/__test__/components/KeyActions.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use TypeScript import specifiers with .js extensions for ESM compatibility
Never use the any type; prefer precise typing
Avoid type casting; model proper types from the start

Files:

  • web/__test__/components/Registration.test.ts
  • api/src/unraid-api/graph/utils/form-utils.ts
  • web/__test__/components/Activation/ActivationSteps.test.ts
  • web/__test__/components/KeyActions.test.ts
  • api/src/types/jsonforms-i18n.d.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/server.test.ts
{api,web}/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{api,web}/**/*.{test,spec}.{ts,tsx}: For error tests, use .rejects.toThrow() without arguments; avoid asserting exact error messages unless that format is the subject
Focus tests on behavior, not implementation details
Avoid brittle tests tied to exact error or log wording
Use mocks as nouns, not verbs
Always await async operations before making assertions
Place all mock declarations at the top level; use factory functions for module mocks; clear mocks between tests

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/ActivationSteps.test.ts
  • web/__test__/components/KeyActions.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/server.test.ts
web/__test__/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place Vue component tests under web/test; run with pnpm test

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/ActivationSteps.test.ts
  • web/__test__/components/KeyActions.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/server.test.ts
web/__test__/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

web/__test__/**/*.{test,spec}.{ts,tsx}: Use mount from Vue Test Utils for component testing
Stub complex child components that aren’t the focus of the test
Mock external dependencies and services in component tests
Test component behavior and output, not implementation details
Use createTestingPinia() for mocking stores in components
Prefer semantic queries like find('button') over data-test IDs
Use await nextTick() for DOM updates before assertions
For store tests, use createPinia() and setActivePinia
Only use createTestingPinia when its special features are needed
Let stores initialize with natural default state; don’t mock the store under test

Files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/ActivationSteps.test.ts
  • web/__test__/components/KeyActions.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/server.test.ts
api/src/unraid-api/**

📄 CodeRabbit inference engine (.cursor/rules/api-rules.mdc)

Prefer adding new files to the Nest repo at api/src/unraid-api/ instead of legacy code

Files:

  • api/src/unraid-api/graph/utils/form-utils.ts
api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

cache-manager v7 TTL values must be in milliseconds (e.g., 600000 for 10 minutes)

Files:

  • api/src/unraid-api/graph/utils/form-utils.ts
  • api/src/types/jsonforms-i18n.d.ts
**/components/**/*.vue

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

Some Vue files may require explicit imports like ref or computed due to Nuxt auto-imports not applying in tests

Files:

  • web/src/components/KeyActions.vue
  • web/src/components/Activation/ActivationSteps.vue
  • web/src/components/Modals.standalone.vue
  • web/src/components/Activation/ActivationModal.vue
**/__test__/store/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

**/__test__/store/**/*.ts: For Pinia store unit tests, initialize with setActivePinia(createPinia()) rather than createTestingPinia, unless testing features from createTestingPinia are explicitly needed
Do not mock the store under test; allow the store to initialize with its natural default state
In store tests, verify action side effects and state changes, and assert that actions are called with the correct parameters
In store tests, mock external dependencies used by the store and verify interactions with those mocks
Test computed properties/getters by accessing them directly and verifying derived state
Thoroughly test async store actions: assert intermediate loading state, await completion, and check final state

Files:

  • web/__test__/store/server.test.ts
**/{store,stores}/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/web-testing-rules.mdc)

In store files, explicitly import Vue reactivity utilities (e.g., computed, ref, watchEffect); do not rely on Nuxt auto-imports

Files:

  • web/__test__/store/server.test.ts
🧠 Learnings (24)
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : Use mount from Vue Test Utils for component testing

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Do not rely on Nuxt auto-imports in tests; import required Vue utilities explicitly in test files

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Component tests should use mount from vue/test-utils

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Activation/ActivationSteps.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/store/**/*.ts : In store tests, mock external dependencies used by the store and verify interactions with those mocks

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/store/server.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : Use createTestingPinia() for mocking stores in components

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/store/server.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Use factory functions in vi.mock calls to define mocks and avoid hoisting pitfalls

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/server.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Use createTestingPinia() to mock Pinia stores when testing Vue components

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
  • web/__test__/store/server.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Remember that vi.mock calls are hoisted; avoid mixing mock declarations and module mocks incorrectly

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/SsoButton.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Mock external dependencies and services in component tests (e.g., vi.mock for helper modules)

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/Auth.test.ts
  • web/__test__/components/Brand/Avatar.test.ts
  • web/__test__/components/SsoButton.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Place module mock declarations (vi.mock) at the top level of the test file to avoid hoisting issues

Applied to files:

  • web/__test__/components/Registration.test.ts
  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/SsoButton.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript import specifiers with .js extensions for ESM compatibility

Applied to files:

  • api/.eslintrc.ts
📚 Learning: 2025-05-23T21:59:29.632Z
Learnt from: elibosley
PR: unraid/api#1408
File: web/components/ApiKey/PermissionCounter.vue:6-6
Timestamp: 2025-05-23T21:59:29.632Z
Learning: This codebase uses ESM (ECMAScript Modules) and requires .js extensions in import statements, even when importing from TypeScript files, as the imports refer to the compiled JavaScript output.

Applied to files:

  • api/.eslintrc.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : Test component behavior and output, not implementation details

Applied to files:

  • web/__test__/components/KeyActions.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Assert rendered text with wrapper.text(), check attributes via element.attributes(), and verify element existence with expect(element.exists()).toBe(true)

Applied to files:

  • web/__test__/components/KeyActions.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to {**/*.test.ts,**/__test__/{components,store}/**/*.ts} : Reset/clear mocks between tests using vi.clearAllMocks() (and vi.resetAllMocks() when appropriate) to ensure isolation

Applied to files:

  • web/__test__/components/Wrapper/mount-engine.test.ts
  • web/__test__/components/SsoButton.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : Test component interactions (clicks, inputs) using await element.trigger and setValue, and verify emitted events via wrapper.emitted()

Applied to files:

  • web/__test__/components/Auth.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : For store tests, use createPinia() and setActivePinia

Applied to files:

  • web/__test__/components/Brand/Avatar.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/store/**/*.ts : For Pinia store unit tests, initialize with setActivePinia(createPinia()) rather than createTestingPinia, unless testing features from createTestingPinia are explicitly needed

Applied to files:

  • web/__test__/components/Brand/Avatar.test.ts
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/components/**/*.ts : In component tests that use real store implementations, configure createTestingPinia with stubActions: false when you need real action behavior

Applied to files:

  • web/__test__/components/Brand/Avatar.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Always run pnpm codegen for GraphQL in the web directory

Applied to files:

  • web/package.json
📚 Learning: 2025-07-21T14:00:25.140Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-graphql.mdc:0-0
Timestamp: 2025-07-21T14:00:25.140Z
Learning: Always run `pnpm codegen` for GraphQL code generation in the web directory

Applied to files:

  • web/package.json
📚 Learning: 2025-08-11T15:10:28.150Z
Learnt from: CR
PR: unraid/api#0
File: .cursor/rules/web-testing-rules.mdc:0-0
Timestamp: 2025-08-11T15:10:28.150Z
Learning: Applies to **/__test__/store/**/*.ts : Do not mock the store under test; allow the store to initialize with its natural default state

Applied to files:

  • web/__test__/store/server.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : Let stores initialize with natural default state; don’t mock the store under test

Applied to files:

  • web/__test__/store/server.test.ts
📚 Learning: 2025-09-12T01:36:59.838Z
Learnt from: CR
PR: unraid/api#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-12T01:36:59.838Z
Learning: Applies to web/__test__/**/*.{test,spec}.{ts,tsx} : Mock external dependencies and services in component tests

Applied to files:

  • web/__test__/store/server.test.ts
🧬 Code graph analysis (9)
web/__test__/components/Registration.test.ts (1)
web/__test__/utils/i18n.ts (2)
  • testTranslate (43-47)
  • createTestI18n (49-58)
web/scripts/extract-translations.mjs (1)
api/scripts/extract-translations.mjs (8)
  • raw (90-90)
  • content (143-143)
  • sourceFile (144-144)
  • keys (54-54)
  • keys (145-145)
  • firstArg (71-71)
  • ignorePatterns (10-17)
  • files (133-137)
web/__test__/components/Activation/ActivationSteps.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • createTestI18n (49-58)
web/__test__/components/KeyActions.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • createTestI18n (49-58)
web/__test__/components/Wrapper/mount-engine.test.ts (1)
web/src/components/Wrapper/mount-engine.ts (1)
  • mountUnifiedApp (80-198)
web/__test__/components/Auth.test.ts (1)
web/__test__/utils/i18n.ts (2)
  • testTranslate (43-47)
  • createTestI18n (49-58)
web/__test__/components/Brand/Avatar.test.ts (1)
web/__test__/utils/i18n.ts (2)
  • testTranslate (43-47)
  • createTestI18n (49-58)
web/__test__/components/SsoButton.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • createTestI18n (49-58)
web/__test__/store/server.test.ts (1)
web/__test__/utils/i18n.ts (1)
  • testTranslate (43-47)
🪛 GitHub Check: CodeQL
web/__test__/components/KeyActions.test.ts

[notice] 30-30: Unused variable, import, function or class
Unused variable t.

web/__test__/components/SsoButton.test.ts

[notice] 13-13: Unused variable, import, function or class
Unused import testTranslate.

web/__test__/store/server.test.ts

[notice] 22-22: Unused variable, import, function or class
Unused import createTestI18n.

🔇 Additional comments (25)
api/.eslintrc.ts (1)

44-48: LGTM! Correct ESM import extension rules.

The updated configuration properly enforces ESM import conventions:

  • mjs: 'always' and cjs: 'always' ensure module variant extensions are explicit
  • ts: 'never' and tsx: 'never' correctly prevent TypeScript source extensions in imports (since imports resolve to compiled .js files)

This change aligns with the project's ESM setup and supports the new .mjs scripts added in this PR (e.g., extract-translations.mjs).

Based on learnings: The codebase uses ESM and requires .js extensions in import statements for TypeScript files.

web/__test__/components/Wrapper/mount-engine.test.ts (2)

80-87: LGTM: Proper mock isolation.

Mock cleanup correctly uses mockClear() for call history and mockReset() for implementation reset, ensuring test isolation.


412-427: LGTM: i18n setup tests verify correct behavior.

Tests properly verify that ensureLocale is called with the expected locale (undefined for default, or the window locale when available), focusing on observable behavior rather than implementation details.

web/__test__/components/Brand/Avatar.test.ts (2)

9-9: LGTM!

The import of test i18n utilities is correctly placed and follows explicit import guidelines.


54-54: LGTM!

The addition of createTestI18n() to the global plugins array in each test mount is correct. This ensures the Avatar component has access to i18n functionality during testing, aligning with the test i18n utilities introduced in this PR.

Also applies to: 72-72, 90-90, 109-109

web/src/components/KeyActions.vue (1)

3-3: LGTM! Clean i18n migration.

The component correctly migrates from receiving t as a prop to using the i18n composition API. The translation function is properly initialized and used in the template for both action.text and action.title.

Also applies to: 13-13, 60-61

web/package.json (2)

33-35: LGTM! Well-organized i18n workflow scripts.

The new scripts properly chain extraction and sorting, providing a clean workflow for managing translations.


116-116: No duplicate 'convert' entry detected. The convert package is listed only under dependencies, not in devDependencies.

Likely an incorrect or invalid review comment.

web/__test__/components/KeyActions.test.ts (1)

15-15: LGTM! Proper i18n test setup.

The test correctly imports and uses createTestI18n() to provide i18n context during component mounting. This aligns with the broader i18n testing strategy.

Also applies to: 46-48

web/scripts/extract-translations.mjs (1)

485-514: Good protection against over-deletion.

The script correctly preserves protected keys (jsonforms, descriptions, labels, TS translation keys) and maybeDynamic keys to prevent breaking dynamic translations. This prevents the extractor from removing keys that are referenced indirectly or computed at runtime.

web/__test__/components/Activation/ActivationSteps.test.ts (1)

10-10: LGTM! Consistent i18n test integration.

The test properly adds createTestI18n() to provide i18n context, consistent with the broader test migration strategy.

Also applies to: 63-65

web/src/components/Modals.standalone.vue (1)

34-34: LGTM! ActivationModal migration complete.

ActivationModal now uses the i18n composition API internally and no longer requires the t prop. The gradual migration approach (other modals still using prop-based translation) is appropriate.

web/__test__/store/server.test.ts (1)

24-29: LGTM! Proper i18n mocking for store tests.

The test correctly mocks vue-i18n's useI18n to return testTranslate, enabling the store to resolve translations during tests without requiring full i18n infrastructure.

Also applies to: 127-127

web/__test__/components/Registration.test.ts (1)

19-19: LGTM! Comprehensive i18n test integration.

The test properly:

  • Imports both createTestI18n and testTranslate
  • Mocks vue-i18n while preserving the actual module exports
  • Injects createTestI18n() into the component mount plugins
  • Uses testTranslate for translation resolution

This follows the established testing pattern for i18n-enabled components.

Also applies to: 30-39, 127-127, 178-178

web/__test__/components/Auth.test.ts (2)

16-26: LGTM! Static analysis warning is a false positive.

The i18n mock setup correctly uses the test utilities. testTranslate is used within the mock factory (line 23) to provide the t function to components under test. The static analysis tool doesn't recognize usage within vi.mock factory functions.


73-73: LGTM! Consistent i18n plugin injection.

All mount calls correctly inject the test i18n plugin via createTestI18n(), providing translation context for the Auth component in tests. This aligns with the broader i18n testing strategy across the codebase.

Also applies to: 97-97, 121-121, 142-142

web/__test__/components/SsoButton.test.ts (1)

149-149: LGTM! Consistent i18n plugin injection across all tests.

All nine test cases correctly inject the test i18n plugin via createTestI18n(), ensuring translation context is available for the SsoButtons component.

Also applies to: 174-174, 203-203, 241-241, 290-290, 330-330, 374-374, 413-413, 468-468

web/src/components/Activation/ActivationSteps.vue (3)

2-3: LGTM! Necessary imports for i18n integration.

The addition of computed and useI18n enables the component to dynamically generate localized step content.


44-44: LGTM! Standard i18n setup.

Correctly initializes the translation function using the useI18n composition API.


46-77: LGTM – translation keys verified
All activation.activationSteps.* keys are present in web/src/locales.

web/src/components/Activation/ActivationModal.vue (4)

3-3: LGTM! Standard i18n composition API setup.

The migration from prop-based translation to composition API is correct. This is a breaking change in the component's public API—it no longer accepts t as a prop and instead uses the global i18n instance.

Also applies to: 19-19


28-31: LGTM! Proper reactive translation setup.

Title and description correctly use computed with translation keys for reactive localization.


32-51: LGTM! Documentation buttons properly localized.

The docsButtons computed correctly uses translation keys for button text while preserving all other button properties (links, icons, variants).


78-78: All ActivationModal translation keys verified. Button text properly localized; no missing keys.

api/src/types/jsonforms-i18n.d.ts (1)

6-40: Consolidate redundant module augmentations.
We detected no imports of @jsonforms/core/(lib|src)/models/jsonSchema4(.js)? or jsonSchema7 in the codebase, so it’s unclear which specifiers your consumers actually use. Verify which paths are imported (or re-exported via your package’s exports), then remove unused augmentations and keep only the canonical module(s).

Comment on lines +1 to +4
import '@jsonforms/core/lib/models/jsonSchema4';
import '@jsonforms/core/lib/models/jsonSchema7';
import '@jsonforms/core/src/models/jsonSchema4';
import '@jsonforms/core/src/models/jsonSchema7';
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

🧩 Analysis chain

Verify necessity of side-effect imports for module augmentation.

In TypeScript declaration files, module augmentation via declare module typically does not require explicit side-effect imports. These imports may be redundant.

Additionally, per the coding guidelines, TypeScript imports should use .js extensions for ESM compatibility. If these imports are necessary, consider:

-import '@jsonforms/core/lib/models/jsonSchema4';
-import '@jsonforms/core/lib/models/jsonSchema7';
-import '@jsonforms/core/src/models/jsonSchema4';
-import '@jsonforms/core/src/models/jsonSchema7';
+import '@jsonforms/core/lib/models/jsonSchema4.js';
+import '@jsonforms/core/lib/models/jsonSchema7.js';
+import '@jsonforms/core/src/models/jsonSchema4.js';
+import '@jsonforms/core/src/models/jsonSchema7.js';

However, if these imports serve no purpose beyond TypeScript module resolution, they can likely be removed entirely.

As per coding guidelines.

Run the following script to check @jsonforms/core package structure and determine which module paths are actually exported:


🏁 Script executed:

#!/bin/bash
# Description: Examine @jsonforms/core package.json to identify exported module paths.

# Check if package.json exists and extract exports/main fields
fd -t f "package.json" api/node_modules/@jsonforms/core --exec cat {} \; | jq -r '.exports, .main, .module, .types'

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Fetch package metadata for @jsonforms/core from npm registry
npm view @jsonforms/core --json | jq -r '.exports, .main, .module, .types'

Length of output: 61


Consolidate JSONForms module augmentations to .js ESM paths

  • Remove imports and declare module blocks for @jsonforms/core/src/models/* (these source‐path modules aren’t published).
  • Update remaining side-effect imports and declare module specifiers to use .js extensions, e.g.:
    • @jsonforms/core/lib/models/jsonSchema4.js
    • @jsonforms/core/lib/models/jsonSchema7.js
🤖 Prompt for AI Agents
In api/src/types/jsonforms-i18n.d.ts around lines 1 to 4, remove the side-effect
imports that reference non-published source paths
(@jsonforms/core/src/models/jsonSchema4 and jsonSchema7) and update the
remaining module augmentation imports and any declare module specifiers to use
the published ESM .js paths (e.g. @jsonforms/core/lib/models/jsonSchema4.js and
@jsonforms/core/lib/models/jsonSchema7.js); delete the two src/* import lines
and replace the lib/* import specifiers to include the .js extension so
TypeScript resolves the published ESM modules correctly.

Comment on lines +22 to +42
const labelElement = {
type: 'Label',
text: label,
options: {
description,
},
} as LabelElement;

if (i18nKey) {
(labelElement as any).i18n = i18nKey;
}

const controlElement = {
type: 'Control',
scope: scope,
options: controlOptions,
} as ControlElement;

if (i18nKey) {
(controlElement as any).i18n = i18nKey;
}
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

Eliminate as any on the JSONForms elements.

We now violate the no-any rule while attaching i18n. Widen the element types so the property can be set without casting.

-    const labelElement = {
-        type: 'Label',
-        text: label,
-        options: {
-            description,
-        },
-    } as LabelElement;
-
-    if (i18nKey) {
-        (labelElement as any).i18n = i18nKey;
-    }
+    const labelElement: LabelElement & { i18n?: string } = {
+        type: 'Label',
+        text: label,
+        options: {
+            description,
+        },
+    };
+
+    if (i18nKey) {
+        labelElement.i18n = i18nKey;
+    }
 
-    const controlElement = {
-        type: 'Control',
-        scope: scope,
-        options: controlOptions,
-    } as ControlElement;
-
-    if (i18nKey) {
-        (controlElement as any).i18n = i18nKey;
-    }
+    const controlElement: ControlElement & { i18n?: string } = {
+        type: 'Control',
+        scope,
+        options: controlOptions,
+    };
+
+    if (i18nKey) {
+        controlElement.i18n = i18nKey;
+    }

As per coding guidelines.

📝 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
const labelElement = {
type: 'Label',
text: label,
options: {
description,
},
} as LabelElement;
if (i18nKey) {
(labelElement as any).i18n = i18nKey;
}
const controlElement = {
type: 'Control',
scope: scope,
options: controlOptions,
} as ControlElement;
if (i18nKey) {
(controlElement as any).i18n = i18nKey;
}
const labelElement: LabelElement & { i18n?: string } = {
type: 'Label',
text: label,
options: {
description,
},
};
if (i18nKey) {
labelElement.i18n = i18nKey;
}
const controlElement: ControlElement & { i18n?: string } = {
type: 'Control',
scope,
options: controlOptions,
};
if (i18nKey) {
controlElement.i18n = i18nKey;
}
🤖 Prompt for AI Agents
In api/src/unraid-api/graph/utils/form-utils.ts around lines 22 to 42, the code
uses `as any` to attach an `i18n` property to JSONForms Label and Control
elements; instead, update the TypeScript types so `i18n` can be set without
casting. Add or import widened interfaces (e.g., declare interfaces
LocalizedLabelElement extends LabelElement { i18n?: string } and
LocalizedControlElement extends ControlElement { i18n?: string } or use
intersection types LabelElement & { i18n?: string } and ControlElement & {
i18n?: string }), then type `labelElement` and `controlElement` with those
widened types and set `.i18n = i18nKey` without any casting.

Comment on lines +83 to +101
const labelElement = {
type: 'Label',
text: label,
options: { ...labelOptions, description },
} as LabelElement;

if (i18nKey) {
(labelElement as any).i18n = i18nKey;
}

const controlElement = {
type: 'Control',
scope: scope,
options: controlOptions,
} as ControlElement;

if (i18nKey) {
(controlElement as any).i18n = i18nKey;
}
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

Apply the same typed approach here.

This block repeats the as any pattern; please widen the element types instead.

-    const labelElement = {
-        type: 'Label',
-        text: label,
-        options: { ...labelOptions, description },
-    } as LabelElement;
-
-    if (i18nKey) {
-        (labelElement as any).i18n = i18nKey;
-    }
+    const labelElement: LabelElement & { i18n?: string } = {
+        type: 'Label',
+        text: label,
+        options: { ...labelOptions, description },
+    };
+
+    if (i18nKey) {
+        labelElement.i18n = i18nKey;
+    }
 
-    const controlElement = {
-        type: 'Control',
-        scope: scope,
-        options: controlOptions,
-    } as ControlElement;
-
-    if (i18nKey) {
-        (controlElement as any).i18n = i18nKey;
-    }
+    const controlElement: ControlElement & { i18n?: string } = {
+        type: 'Control',
+        scope,
+        options: controlOptions,
+    };
+
+    if (i18nKey) {
+        controlElement.i18n = i18nKey;
+    }

As per coding guidelines.

📝 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
const labelElement = {
type: 'Label',
text: label,
options: { ...labelOptions, description },
} as LabelElement;
if (i18nKey) {
(labelElement as any).i18n = i18nKey;
}
const controlElement = {
type: 'Control',
scope: scope,
options: controlOptions,
} as ControlElement;
if (i18nKey) {
(controlElement as any).i18n = i18nKey;
}
const labelElement: LabelElement & { i18n?: string } = {
type: 'Label',
text: label,
options: { ...labelOptions, description },
};
if (i18nKey) {
labelElement.i18n = i18nKey;
}
const controlElement: ControlElement & { i18n?: string } = {
type: 'Control',
scope,
options: controlOptions,
};
if (i18nKey) {
controlElement.i18n = i18nKey;
}
🤖 Prompt for AI Agents
In api/src/unraid-api/graph/utils/form-utils.ts around lines 83 to 101, the code
uses (labelElement as any).i18n and (controlElement as any).i18n; change this to
a typed approach by widening the element types: either add an optional i18n?:
string property to the LabelElement and ControlElement interfaces (preferred)
or, if you cannot modify the shared interfaces, use local intersection types
when creating the objects (e.g., const labelElement: LabelElement & { i18n?:
string } = ... and const controlElement: ControlElement & { i18n?: string } =
...) and then assign i18nKey without using any casts.

}));

const t = (key: string) => `translated_${key}`;
const t = (key: string) => `${key}`;
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 | 🟡 Minor

Remove unused local translation function.

The local t function defined at line 30 is unused, as confirmed by static analysis. The test now relies on createTestI18n() to provide translation context, making this local function redundant.

Apply this diff to remove the unused function:

-const t = (key: string) => `${key}`;
-
 describe('KeyActions', () => {
📝 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
const t = (key: string) => `${key}`;
describe('KeyActions', () => {
🧰 Tools
🪛 GitHub Check: CodeQL

[notice] 30-30: Unused variable, import, function or class
Unused variable t.

🤖 Prompt for AI Agents
In web/__test__/components/KeyActions.test.ts around line 30, there's an unused
local translation function `const t = (key: string) => `${key}`;` which is
redundant because tests use createTestI18n(); remove this line from the file to
eliminate the dead code and keep imports/fixtures intact.


import { WebguiState } from '~/composables/services/webgui';
import { useServerStore } from '~/store/server';
import { createTestI18n, testTranslate } from '../utils/i18n';
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 | 🟡 Minor

Remove unused import.

createTestI18n is imported but never used in this store test file, as confirmed by static analysis. Store tests only need testTranslate for mocking useI18n.

Apply this diff:

-import { createTestI18n, testTranslate } from '../utils/i18n';
+import { testTranslate } from '../utils/i18n';
📝 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
import { createTestI18n, testTranslate } from '../utils/i18n';
import { testTranslate } from '../utils/i18n';
🧰 Tools
🪛 GitHub Check: CodeQL

[notice] 22-22: Unused variable, import, function or class
Unused import createTestI18n.

🤖 Prompt for AI Agents
In web/__test__/store/server.test.ts around line 22, the import statement
includes createTestI18n which is unused; remove createTestI18n from the import
list so only testTranslate is imported to eliminate the unused import warning
and keep the test file minimal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant