chore: cleanup standards (#6970)

* chore: cleanup standards & skills

* remove: figma mcp doc, not needed anymore
This commit is contained in:
Calum H.
2026-08-10 15:15:44 +00:00
committed by GitHub
parent 98cb6b6b44
commit e8aee15664
39 changed files with 1271 additions and 1130 deletions
+78 -51
View File
@@ -1,40 +1,51 @@
- [Internationalization (i18n)](#internationalization-i18n)
- [Translatable Strings](#translatable-strings)
- [Message Definitions](#message-definitions)
- [Rendering Messages](#rendering-messages)
- [ICU Message Format](#icu-message-format)
- [Writing Translation-Friendly Strings](#writing-translation-friendly-strings)
- [Rich-Text Messages](#rich-text-messages)
- [Vue/ICU Delimiter Collisions](#vueicu-delimiter-collisions)
- [Internationalization](#internationalization)
- [Translatable strings](#translatable-strings)
- [Message definitions](#message-definitions)
- [Render messages](#render-messages)
- [ICU message format](#icu-message-format)
- [Write strings for translation](#write-strings-for-translation)
- [Rich-text messages](#rich-text-messages)
- [Vue and ICU delimiter conflicts](#vue-and-icu-delimiter-conflicts)
- [Imports](#imports)
- [Reference Examples](#reference-examples)
- [Reference examples](#reference-examples)
# Internationalization (i18n)
# Internationalization
All user-visible strings in Vue SFCs must use the localization system from `@modrinth/ui`. No hard-coded English strings should appear in templates or script — everything comes from `formatMessage` or `<IntlFormatted>`.
Use the `@modrinth/ui` localization system for all user-visible strings in Vue single-file components (SFCs).
Do not put hard-coded English text in templates or scripts. Get all user-visible text from `formatMessage` or `<IntlFormatted>`.
## Translatable Strings
User-visible strings include: inner text, `alt` attributes, `placeholder` attributes, button labels, dropdown option labels, notification messages, etc.
Translate these user-visible items:
Dynamic expressions (`{{ user.name }}`) and HTML tags are not translatable strings — only static human-readable text.
- Inner text.
- `alt` and `placeholder` attributes.
- Button and dropdown-option labels.
- Notification and error messages.
Do not translate dynamic expressions, HTML tag names, CSS classes, internal identifiers, or log messages.
In `{{ user.name }}`, only the static text around the expression needs translation.
## Message Definitions
Messages are defined with `defineMessage` or `defineMessages` from `@modrinth/ui` in `<script setup>`. Each message has a unique `id` and a `defaultMessage` containing the English string:
Use `defineMessage` or `defineMessages` from `@modrinth/ui` in `<script setup>`.
Give each message a unique `id`. Put the English text in `defaultMessage`:
```ts
const messages = defineMessages({
welcomeTitle: { id: 'auth.welcome.title', defaultMessage: 'Welcome' },
welcomeDescription: { id: 'auth.welcome.description', defaultMessage: "You're now part of the community…" },
welcomeDescription: { id: 'auth.welcome.description', defaultMessage: 'You are now part of the community.' },
})
```
Message `id`s should be descriptive and stable (e.g. `error.generic.default.title`). Group related messages together with `defineMessages`.
Use descriptive, stable message IDs, such as `error.generic.default.title`. Put related messages in one `defineMessages` object.
## Rendering Messages
## Render Messages
Use `useVIntl()` from `@modrinth/ui` for simple string formatting:
Use `useVIntl()` from `@modrinth/ui` to format simple strings:
```ts
const { formatMessage } = useVIntl()
@@ -47,51 +58,63 @@ const { formatMessage } = useVIntl()
## ICU Message Format
Dynamic values use ICU placeholders in `defaultMessage`:
Use ICU placeholders for dynamic values in `defaultMessage`:
- **Variables:** `'Hello, {name}!'`
- **Numbers/dates/times:** `'{price, number, ::currency/USD}'`
- **Plurals/selects:** `'{count, plural, one {# message} other {# messages}}'`
- Variable: `'Hello, {name}!'`
- Number, date, or time: `'{price, number, ::currency/USD}'`
- Plural or selection: `'{count, plural, one {# message} other {# messages}}'`
## Writing Translation-Friendly Strings
## Write Strings for Translation
ICU gives you powerful tools (plurals, selects, nested expressions), but translators in other languages face constraints that English doesn't have:
ICU supports plurals, selections, and nested expressions. Languages can have different grammar rules.
- **Word order varies by language.** Don't assume `{action} {noun}` works everywhere — some languages need `{noun} {action}` or require prepositions between them.
- **Plurals aren't just "add an s".** Many languages change internal parts of a word or phrase for pluralization, not just the ending. A simple `{count} {itemType}` breaks if `itemType` is always singular.
- **Grammatical gender affects surrounding words.** Articles, adjectives, and verbs may change based on whether a noun is masculine or feminine. If a variable like `{contentType}` can be "shader" or "mod", translators may need to inflect surrounding text differently for each.
- Word order changes between languages. Do not assume that `{action} {noun}` operates in all languages.
- Plural forms can change a complete word or phrase. Do not only add an `s` to make a plural.
- Grammatical gender can change articles, adjectives, and verbs. Give translators a separate branch for each content type.
### Guidelines
1. **Use `select` for content types, not bare variables.** When a variable represents different content types (mod, shader, modpack, etc.), pass a key and use ICU `select` so translators can write type-specific forms:
1. Use `select` for content types. Do not use a bare variable for a content type.
```
// Bad — translators can't inflect around a pre-rendered noun
Pass a content-type key. Then, use ICU `select` so translators can write a specific form for each type:
```text
// Incorrect. Translators cannot change the grammar around this rendered noun.
'Delete {count} {itemType}'
// Good — translators can write entirely different phrases per type
// Correct. Translators can write a different phrase for each type.
'Delete {count} {contentType, select, mod {{count, plural, one {mod} other {mods}}} shader {{count, plural, one {shader} other {shaders}}} other {items}}'
```
This lets translators write entirely different noun forms per branch, which many languages require.
This structure lets translators write different noun forms in each branch.
2. **Prefer separate messages over complex ICU when branches diverge significantly.** If the singular and plural versions of a string are structurally different (not just a noun change), use two separate message IDs rather than one complex ICU expression.
2. Use separate messages when ICU branches have different sentence structures.
3. **Don't concatenate translated strings.** Never build a sentence by joining multiple `formatMessage` calls — the word order may be wrong in other languages. Put the entire sentence in one message.
If singular and plural text have different structures, use two message IDs. Do not make one complex ICU expression.
4. **Keep variables semantic.** Pass `contentType: 'mod'` (a key), not `contentType: 'Mod'` (a pre-rendered display string). Translators can then map each key to the correct form in their language.
3. Do not join translated strings.
5. **Test with long strings.** German and Finnish words can be 2-3x longer than English equivalents. Ensure UI layouts don't break with longer text.
Do not make a sentence from multiple `formatMessage` calls. Put the complete sentence in one message.
4. Use semantic variable values.
Pass `contentType: 'mod'` as a key. Do not pass `contentType: 'Mod'` as rendered text.
The translator can map each key to the correct form.
5. Test the UI with long strings.
Some translated words can be two or three times longer than the English words. Make sure that the layout remains correct.
## Rich-Text Messages
When a message contains links or markup, wrap the relevant ranges with named tags in `defaultMessage`:
When a message contains links or markup, put named tags around the applicable text in `defaultMessage`:
```
"By creating an account, you agree to our <terms-link>Terms</terms-link> and <privacy-link>Privacy Policy</privacy-link>."
```text
"When you create an account, you agree to the <terms-link>Terms</terms-link> and <privacy-link>Privacy Policy</privacy-link>."
```
Render with the `<IntlFormatted>` component using named slots:
Use named slots in `<IntlFormatted>` to render the tags:
```vue
<IntlFormatted :message-id="messages.tosLabel">
@@ -108,7 +131,11 @@ Render with the `<IntlFormatted>` component using named slots:
</IntlFormatted>
```
For simple emphasis (`'Welcome to <strong>Modrinth</strong>!'`):
Use this pattern for simple emphasis:
```text
'Welcome to <strong>Modrinth</strong>!'
```
```vue
<template #strong="{ children }">
@@ -116,7 +143,7 @@ For simple emphasis (`'Welcome to <strong>Modrinth</strong>!'`):
</template>
```
For complex child handling, use `normalizeChildren` from `@modrinth/ui`:
Use `normalizeChildren` from `@modrinth/ui` for complex child content:
```vue
<template #bold="{ children }">
@@ -124,20 +151,20 @@ For complex child handling, use `normalizeChildren` from `@modrinth/ui`:
</template>
```
## Vue/ICU Delimiter Collisions
## Vue and ICU Delimiter Conflicts
If an ICU placeholder ends right before `}}` in a Vue template, insert a space (`} }`) to avoid parsing issues.
If an ICU placeholder ends immediately before `}}`, add a space. Use `} }` to prevent a Vue parser error.
## Imports
All i18n utilities come from `@modrinth/ui`:
Get all internationalization utilities from `@modrinth/ui`:
- `defineMessage` / `defineMessages` — message definitions
- `useVIntl` — composable providing `formatMessage`
- `IntlFormatted` — component for rich-text messages
- `normalizeChildren` — helper for complex rich-text slot children
- `defineMessage` and `defineMessages`: Define messages.
- `useVIntl`: Supplies `formatMessage`.
- `IntlFormatted`: Renders rich-text messages.
- `normalizeChildren`: Normalizes complex rich-text slot children.
## Reference Examples
- Variables and plurals: `apps/frontend/src/pages/frog.vue`
- Rich-text with link tags: `apps/frontend/src/error.vue`
- Variables and plurals: `apps/frontend/src/pages/frog.vue`.
- Rich text with link tags: `apps/frontend/src/error.vue`.