feat(seo-plugin): i18n support for UI components and fix favicon infinite loop (#299, #300)#301
feat(seo-plugin): i18n support for UI components and fix favicon infinite loop (#299, #300)#301khuepm wants to merge 5 commits intodirectus-labs:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds localization support to the seo-plugin bundle UI and fixes the search preview favicon error handling to prevent repeated failing requests.
Changes:
- Replaces hardcoded UI strings in Vue components with
vue-i18ntranslation keys and adds new translation dictionaries. - Converts sitemap/search-control field labels/options/tooltips to use translated keys at render time.
- Prevents the favicon fallback from triggering an infinite request loop by switching to a non-
imgfallback on load error.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/seo-plugin/src/shared/components/SearchPreview.vue | Uses i18n strings and replaces the favicon error fallback with a conditional fallback icon. |
| packages/seo-plugin/src/shared/components/OgImagePreview.vue | Localizes several UI strings for the OG image preview panel. |
| packages/seo-plugin/src/seo-interface/interface.vue | Injects and merges plugin locale messages; localizes tab labels and form labels/tooltips. |
| packages/seo-plugin/src/seo-interface/fields.ts | Replaces literal labels/tooltips/options with translation keys. |
| packages/seo-plugin/src/seo-interface/components/TitleField.vue | Localizes label/placeholder/tooltip. |
| packages/seo-plugin/src/seo-interface/components/MetaDescriptionField.vue | Localizes label/placeholder/tooltip. |
| packages/seo-plugin/src/seo-interface/components/FocusKeyphrase.vue | Localizes label/placeholder/tooltips/hint text. |
| packages/seo-plugin/src/seo-interface/components/SeoFieldWrapper.vue | Localizes the recommendation/counter hint text. |
| packages/seo-plugin/src/seo-interface/translations/en-US.yaml | Adds new interface translation keys (English). |
| packages/seo-plugin/src/seo-interface/translations/vi-VN.yaml | Adds new interface translation keys (Vietnamese). |
| packages/seo-plugin/src/seo-display/display.vue | Injects and merges plugin locale messages; localizes display UI text. |
| packages/seo-plugin/src/seo-display/translations/en-US.yaml | Switches namespace to seo_plugin and adds new UI keys (English). |
| packages/seo-plugin/src/seo-display/translations/vi-VN.yaml | Adds new UI keys (Vietnamese). |
| packages/seo-plugin/src/lang/index.ts | Adds bundled translation objects used for runtime mergeLocaleMessage(). |
| packages/seo-plugin/package.json | Bumps package version to 1.1.2. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <div class="recommendations"> | ||
| <h4>Recommend Image Size</h4> | ||
| <h4>{{ t('seo_plugin.ui.recommend_image_size') }}</h4> | ||
| <p>• 1200 X 630 pixels<br>• 1.91:1 aspect ratio</p> |
There was a problem hiding this comment.
This section still contains user-facing English strings (e.g. "pixels" / "aspect ratio") even when the rest of the component is translated. To keep i18n coverage consistent, move the size/recommendation text into translation keys (or split it into translatable parts).
| <p>• 1200 X 630 pixels<br>• 1.91:1 aspect ratio</p> | |
| <p v-html="t('seo_plugin.ui.recommend_image_size_details')" /> |
| const { t, mergeLocaleMessage } = useI18n(); | ||
|
|
||
| pluginTranslations.forEach((lang) => { | ||
| mergeLocaleMessage(lang.language, lang.translation); | ||
| }); |
There was a problem hiding this comment.
mergeLocaleMessage is executed on every component instance creation. If multiple SEO interface instances are rendered in the app, this will repeatedly merge the same messages and add avoidable overhead. Consider moving this into a shared init helper/module with a one-time guard (or only merging for the active locale).
| const { t, mergeLocaleMessage } = useI18n(); | ||
|
|
||
| pluginTranslations.forEach((lang) => { | ||
| mergeLocaleMessage(lang.language, lang.translation); | ||
| }); |
There was a problem hiding this comment.
mergeLocaleMessage is executed on every component instance creation. If multiple SEO display instances are rendered, this will repeatedly merge the same messages and add avoidable overhead. Consider moving this into a shared init helper/module with a one-time guard (or only merging for the active locale).
| export default [ | ||
| { | ||
| language: 'en-US', | ||
| translation: { | ||
| seo_plugin: { | ||
| fields: { | ||
| title: 'Title', | ||
| meta_description: 'Meta Description', |
There was a problem hiding this comment.
This file duplicates the same translation content already present in src/seo-interface/translations/* and src/seo-display/translations/*. Keeping multiple sources of truth increases the chance of drift and inflates the bundle size. Consider choosing a single canonical source (e.g. generate this TS module from the YAML at build time, or remove the YAML if bundling is required).
|
|
||
| const { t } = useI18n(); | ||
|
|
||
| const hasFaviconError = ref(false); |
There was a problem hiding this comment.
hasFaviconError is never reset, so once the favicon fails to load the component will permanently render the fallback icon even if the favicon URL becomes valid later (e.g., user changes the favicon or navigation updates it). Consider resetting the flag when the computed favicon URL changes (watch) or keying the <img> by the favicon URL so a new URL retries loading.
| <small class="hint"> | ||
| <span>{{ state.message }}</span> | ||
| {{ rule.minLength }}-{{ rule.maxLength }} characters recommended. (Current: {{ state.length ?? '?' }}) | ||
| {{ t('seo_plugin.interface.character_recommendation', { min: rule.minLength, max: rule.maxLength }) }}. ({{ t('seo_plugin.interface.current_length', { length: state.length ?? '?' }) }}) |
There was a problem hiding this comment.
The status label state.message is still hardcoded English coming from useSeoField (e.g. "Too long", "Ideal length"). Since the rest of the hint is now translated, this will result in mixed-language UI. Consider translating based on state.status (or storing translation keys in the status config) and aligning keys with the actual status values (too-long/too-short vs current too_long/too_short in translations).
Description
This PR introduces two major enhancements and fixes for the
seo-pluginextension, addressing both UI localization capability and a performance bug during search preview rendering.Resolves #299
Resolves #300
Changes Made
1. Multi-language (i18n) Support
$t()translation method across all Vue components (tabs, labels, field titles, placeholders, hints, and error notices).seo_plugin.prefix.vi-VNto handle these translation pairs.mergeLocaleMessage()directly into the main Vue bundle via src/lang/index.ts to ensure translations work seamlessly within the Directus Extension bundle constraints.2. SearchPreview Favicon Infinite Loop Fix
window.location.originloop query due to missing error boundaries.@errorevent handler (handleFaviconError) to correctly capture image load failures.showFavicon = false) instead of continually fetching.Verification
seo-plugininterface actively changing between English and Vietnamese interface modes within a local Directus environment.npm run buildsucceeds correctly with the translations bundled within app.js and api.js.Checklist
dist/elements.