Files
AstralRinth/.github/instructions/i18n-convert.instructions.md
Calum H. 042451bad6 feat: i18n switcher in app-frontend (#4990)
* feat: app i18n stuff

* feat: locale switching on load

* feat: db migration

* feat: polish + fade indicator impl onto TabbedModal

* fix: prepr checks

* fix: remove staging lock for language switching

* fix: lint
2025-12-29 19:41:39 +00:00

4.0 KiB

applyTo
applyTo
**/*.vue

You are given a Nuxt/Vue single-file component (.vue). Your task is to convert every hard-coded natural-language string in the into our localization system using vue-i18n with utilities from @modrinth/ui.

Please follow these rules precisely:

  1. Identify translatable strings
  • Scan the for all user-visible strings (inner text, alt attributes, placeholders, button labels, etc.). Do not extract dynamic expressions (like {{ user.name }}) or HTML tags. Only extract static human-readable text.
  • There may be strings within the <script> block, e.g dropdown option labels, notifications etc.
    1. Create message definitions
    • In the <script setup> block, import defineMessage or defineMessages from @modrinth/ui.
    • For each extracted string, define a message with a unique id (use a descriptive prefix based on the component path, e.g. auth.welcome.long-title) and a defaultMessage equal to the original English string. Example: const messages = defineMessages({ welcomeTitle: { id: 'auth.welcome.title', defaultMessage: 'Welcome' }, welcomeDescription: { id: 'auth.welcome.description', defaultMessage: 'You're now part of the community…' }, })
    1. Handle variables and ICU formats
    • Replace dynamic parts with ICU placeholders: "Hello, ${user.name}!" → {name} and defaultMessage: 'Hello, {name}!'
    • For numbers/dates/times, use ICU options (e.g., currency): {price, number, ::currency/USD}
    • For plurals/selects, use ICU: '{count, plural, one {# message} other {# messages}}'
    1. Rich-text messages (links/markup)
    • In defaultMessage, wrap link/markup ranges with tags, e.g.: "By creating an account, you agree to our Terms and Privacy Policy."
    • Render rich-text messages with <IntlFormatted> from @modrinth/ui using named slots: <template #terms-link="{ children }">
    <template #privacy-link="{ children }">

  • For simple emphasis: 'Welcome to <strong>Modrinth</strong>!' with a slot: <template #strong="{ children }">
  • For more complex child handling, use normalizeChildren from @modrinth/ui: <template #bold="{ children }">
    1. Formatting in templates
    • Import and use useVIntl() from @modrinth/ui; prefer formatMessage for simple strings: const { formatMessage } = useVIntl() <button>{{ formatMessage(messages.welcomeTitle) }}</button>
    • Pass variables as a second argument: {{ formatMessage(messages.greeting, { name: user.name }) }}
    1. Naming conventions and id stability
    • Make ids descriptive and stable (e.g., error.generic.default.title). Group related messages with defineMessages.
    1. Avoid Vue/ICU delimiter collisions
    • If an ICU placeholder would end right before }} in a Vue template, insert a space so it becomes } } to avoid parsing issues.
    1. Update imports and remove literals
    • Ensure imports from @modrinth/ui are present: defineMessage/defineMessages, useVIntl, IntlFormatted, and optionally normalizeChildren.
    • Replace all hard-coded strings with formatMessage(...) or <IntlFormatted> and remove the literals.
    1. Preserve functionality
    • Do not change logic, layout, reactivity, or bindings—only refactor strings into i18n.

    Use existing patterns from our codebase:

    • Variables/plurals: see apps/frontend/src/pages/frog.vue
    • Rich-text link tags: see apps/frontend/src/pages/auth/welcome.vue and apps/frontend/src/error.vue

    When you finish, there should be no hard-coded English strings left in the template—everything comes from formatMessage or <IntlFormatted>.