Skip to content

feat(seo-plugin): i18n support for UI components and fix favicon infinite loop (#299, #300)#301

Open
khuepm wants to merge 5 commits intodirectus-labs:mainfrom
khuepm:main
Open

feat(seo-plugin): i18n support for UI components and fix favicon infinite loop (#299, #300)#301
khuepm wants to merge 5 commits intodirectus-labs:mainfrom
khuepm:main

Conversation

@khuepm
Copy link
Copy Markdown

@khuepm khuepm commented Feb 24, 2026

Description

This PR introduces two major enhancements and fixes for the seo-plugin extension, addressing both UI localization capability and a performance bug during search preview rendering.

Resolves #299
Resolves #300

Changes Made

1. Multi-language (i18n) Support

  • Replaced hardcoded English string literals with the $t() translation method across all Vue components (tabs, labels, field titles, placeholders, hints, and error notices).
  • Added core translation keys systematically using the seo_plugin. prefix.
  • Created structured YAML language files en-US.yaml and vi-VN to handle these translation pairs.
  • Injected mergeLocaleMessage() directly into the main Vue bundle via src/lang/index.ts to ensure translations work seamlessly within the Directus Extension bundle constraints.
  • Transformed static Dropdown options in fields.ts (e.g. Change Frequency, Priority) into dynamic translated options on render.

2. SearchPreview Favicon Infinite Loop Fix

  • Fixed an issue in SearchPreview.vue where a broken favicon URL caused an infinite window.location.origin loop query due to missing error boundaries.
  • Added an @error event handler (handleFaviconError) to correctly capture image load failures.
  • Implemented a fallback mechanism to hide the favicon gracefully (showFavicon = false) instead of continually fetching.

Verification

  • Validated that the SearchPreview component successfully catches image errors without looping.
  • Tested the seo-plugin interface actively changing between English and Vietnamese interface modes within a local Directus environment.
  • Verified that a full npm run build succeeds correctly with the translations bundled within app.js and api.js.

Checklist

  • Tested changes locally.
  • Rebuilt dist/ elements.
  • Code follows the extension's existing structure and coding standards.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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-i18n translation 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-img fallback 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>
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
<p>• 1200 X 630 pixels<br>• 1.91:1 aspect ratio</p>
<p v-html="t('seo_plugin.ui.recommend_image_size_details')" />

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +40
const { t, mergeLocaleMessage } = useI18n();

pluginTranslations.forEach((lang) => {
mergeLocaleMessage(lang.language, lang.translation);
});
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +114 to +118
const { t, mergeLocaleMessage } = useI18n();

pluginTranslations.forEach((lang) => {
mergeLocaleMessage(lang.language, lang.translation);
});
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +8
export default [
{
language: 'en-US',
translation: {
seo_plugin: {
fields: {
title: 'Title',
meta_description: 'Meta Description',
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.

const { t } = useI18n();

const hasFaviconError = ref(false);
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines 24 to +26
<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 ?? '?' }) }})
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
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.

Feature: Add Multi-language (i18n) Support for UI Components Fallback favicon in seo-plugin extension being called infinitely

3 participants