Localization Development
This guide explains how to work with localization in Nuxt Users development, including adding new translations, testing, and quality assurance.
Development Scripts
The module includes two automated checking scripts to maintain translation quality:
check:translations
Scans Vue files for hardcoded strings that should use the t() translation function.
yarn check:translationsWhat it checks:
- Template strings in Vue components
- Detects user-facing text that isn't translated
- Filters out technical strings (CSS classes, SVG paths, etc.)
Example output:
[Nuxt Users] 🔍 Scanning Vue files in: src/runtime/components
[Nuxt Users] 📄 Found 11 Vue file(s)
[Nuxt Users] ⚠️ Found 1 potential hardcoded string(s):
📁 src/runtime/components/NUsersResetPasswordForm.vue
Line 11:9
String: "You are resetting your password..."
Context: <p>You are resetting your password...</p>
[Nuxt Users] 💡 Tip: Replace hardcoded strings with t() functioncheck:locale-consistency
Ensures Hungarian formal and informal translation overrides are kept in sync.
yarn check:locale-consistencyWhat it checks:
- All formal variant keys have informal equivalents
- All informal variant keys have formal equivalents
- Reports mismatches with statistics
Example output:
[Nuxt Users] 🔍 Checking Hungarian locale formal/informal consistency...
[Nuxt Users] 📊 Statistics:
Base keys: 85
Informal override keys: 20
Formal override keys: 20
[Nuxt Users] ✅ All formal/informal translations are consistent!Both scripts run automatically during the release process.
File Structure
src/runtime/
├── locales/
│ ├── index.ts # Exports all locales
│ ├── en.ts # English translations
│ └── hu.ts # Hungarian translations (informal + formal)
├── utils/
│ └── locale.ts # Translation utilities
└── composables/
└── useNuxtUsersLocale.ts # Composable for translationsAdding a New Language
1. Create Locale File
Create a new file in src/runtime/locales/:
// src/runtime/locales/de.ts
import type { LocaleMessages } from '../../types'
export const de: LocaleMessages = {
common: {
email: 'E-Mail',
password: 'Passwort',
name: 'Name',
// ... all common keys
},
login: {
title: 'Willkommen zurück',
subtitle: 'Melden Sie sich an',
// ... all login keys
},
// ... all other sections
}2. Export from Index
Add your locale to src/runtime/locales/index.ts:
import { en } from './en'
import { hu, huFormal } from './hu'
import { de } from './de' // Add this
export const defaultLocaleMessages: Record<string, LocaleMessages> = {
en,
hu,
'hu-formal': huFormal,
de // Add this
}3. Test Your Translation
Create an integration test:
// test/integration/locale-de.test.ts
import { describe, it, expect } from 'vitest'
import { getTranslation } from '../../src/runtime/utils/locale'
describe('German Locale', () => {
it('should translate login strings', () => {
expect(getTranslation('login.title', 'de')).toBe('Willkommen zurück')
expect(getTranslation('login.submit', 'de')).toBe('Anmelden')
})
it('should have all required keys', () => {
const keys = [
'login.title',
'login.subtitle',
'register.title',
'resetPassword.titleReset'
// ... test key translations exist
]
keys.forEach(key => {
const translation = getTranslation(key, 'de')
expect(translation).not.toBe(key) // Should not return key itself
})
})
})4. Run Quality Checks
# Check for hardcoded strings
yarn check:translations
# Run tests
yarn test:unit test/integration/locale-de.test.tsAdding Translation Keys
When adding new features that require translations:
1. Add to English (Base)
Always start with English in src/runtime/locales/en.ts:
export const en: LocaleMessages = {
// ... existing keys
myNewFeature: {
title: 'My New Feature',
description: 'This is a new feature',
action: 'Do Something'
}
}2. Add to All Other Locales
Add equivalent keys to all supported locales:
// src/runtime/locales/hu.ts
const huBase: LocaleMessages = {
// ... existing keys
myNewFeature: {
title: 'Új Funkció',
description: 'Ez egy új funkció',
action: 'Csinálj Valamit'
}
}3. Add Tests
Test the new translations:
it('should translate myNewFeature keys', () => {
expect(getTranslation('myNewFeature.title', 'en')).toBe('My New Feature')
expect(getTranslation('myNewFeature.title', 'hu')).toBe('Új Funkció')
})4. Use in Components
<script setup>
const { t } = useNuxtUsersLocale()
</script>
<template>
<div>
<h2>{{ t('myNewFeature.title') }}</h2>
<p>{{ t('myNewFeature.description') }}</p>
<button>{{ t('myNewFeature.action') }}</button>
</div>
</template>Hungarian Formal/Informal Variants
Hungarian has two politeness levels that must be maintained:
Base (Shared)
Common, non-contextual terms go in huBase:
const huBase: LocaleMessages = {
common: {
email: 'E-mail', // Same in both variants
password: 'Jelszó', // Same in both variants
name: 'Név' // Same in both variants
}
}Informal Overrides
Personal, casual terms for the hu locale:
const huInformalOverrides: LocaleMessages = {
login: {
rememberMe: 'Emlékezz rám', // "you" informal
forgotPassword: 'Elfelejtetted...?' // "you" informal
}
}Formal Overrides
Professional, polite terms for the hu-formal locale:
const huFormalOverrides: LocaleMessages = {
login: {
rememberMe: 'Maradjon bejelentkezve', // "you" formal
forgotPassword: 'Elfelejtette...?' // "you" formal
}
}Consistency Rule
Every key in formal must have an informal equivalent and vice versa.
The check:locale-consistency script enforces this:
yarn check:locale-consistencyIf you add a key to one variant, add it to the other:
// ✅ Correct - both variants have the key
const huInformalOverrides = {
myFeature: { action: 'Csináld' } // informal
}
const huFormalOverrides = {
myFeature: { action: 'Tegye' } // formal
}
// ❌ Wrong - only formal has it
const huFormalOverrides = {
myFeature: { action: 'Tegye' }
}
// Missing in informal - check will fail!Translation Utilities
getTranslation()
The core translation function in src/runtime/utils/locale.ts:
export const getTranslation = (
key: string,
locale: string = 'en',
customTexts?: Record<string, LocaleMessages>,
fallbackLocale: string = 'en',
params?: (string | number)[]
): stringFeatures:
- Dot notation key lookup (
login.title) - Fallback chain: current locale → fallback locale → English
- Parameter replacement (
{0},{1}, etc.) - Custom text merging with defaults
- Development-mode parameter count validation
deepMerge()
Merges custom translations with defaults:
export const deepMerge = (
target: LocaleMessages,
source: LocaleMessages
): LocaleMessagesUsed to merge user-provided custom translations with module defaults.
getNestedValue()
Retrieves nested values using dot notation:
export const getNestedValue = (
obj: LocaleMessages,
path: string
): string | undefinedExample:
getNestedValue(messages, 'login.title')
// Returns messages.login.titleTesting Translations
Unit Tests
Test individual translation functions:
import { describe, it, expect } from 'vitest'
import { getTranslation } from '../../src/runtime/utils/locale'
describe('Translations', () => {
it('should get correct translation', () => {
const result = getTranslation('login.title', 'en')
expect(result).toBe('Welcome Back')
})
it('should handle parameters', () => {
const result = getTranslation('userCard.deleteConfirm', 'en', undefined, 'en', ['John'])
expect(result).toBe('Are you sure you want to delete user John?')
})
it('should fallback to English', () => {
const result = getTranslation('login.title', 'nonexistent')
expect(result).toBe('Welcome Back') // Falls back to English
})
})Integration Tests
Test translations in components:
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi } from 'vitest'
import NUsersLoginForm from '../../src/runtime/components/NUsersLoginForm.vue'
describe('NUsersLoginForm Localization', () => {
it('should display translated strings', () => {
const wrapper = mount(NUsersLoginForm, {
global: {
mocks: {
useRuntimeConfig: () => ({
public: {
nuxtUsers: {
locale: { default: 'hu', fallbackLocale: 'en', texts: {} }
}
}
})
}
}
})
// Check Hungarian translations appear
expect(wrapper.text()).toContain('Üdvözöljük')
})
})Parameter Count Validation
In development mode, the system validates parameter counts:
// Translation
deleteConfirm: 'Delete {0}?'
// Correct usage
t('deleteConfirm', ['John']) // ✅ 1 param expected, 1 provided
// Incorrect usage
t('deleteConfirm', ['John', 'Extra']) // ⚠️ Warns: expected 1, got 2
t('deleteConfirm') // ⚠️ Warns: expected 1, got 0Warning example:
[nuxt-users locale] Parameter count mismatch for key "deleteConfirm":
expected 1 parameter(s), but received 2This only runs in development (NODE_ENV !== 'production').
Best Practices
1. Always Test New Translations
# Run unit tests
yarn test:unit test/unit/locale.test.ts
# Run integration tests
yarn test:unit test/integration/locale-components.test.ts
# Check for hardcoded strings
yarn check:translations
# Check Hungarian consistency
yarn check:locale-consistency2. Keep Keys Organized
Use clear, nested structure:
// ✅ Good structure
{
feature: {
subsection: {
specificKey: 'value'
}
}
}
// ❌ Avoid flat structure
{
featureSubsectionSpecificKey: 'value'
}3. Document New Keys
When adding keys, update:
- User guide documentation (
docs/user-guide/localization.md) - This developer guide if adding new sections
- JSDoc comments in type definitions
4. Use Consistent Terminology
Maintain consistency across languages:
- Use the same term for the same concept
- Keep button labels consistent (e.g., always "Submit" not mixed with "Send")
- Follow established patterns in existing translations
5. Consider Context
Especially for Hungarian formal/informal:
- Login/register forms: Can be friendly (informal) or professional (formal)
- Admin panels: Usually formal
- User profiles: Depends on your app's tone
- Error messages: Keep consistent with rest of app
Continuous Integration
The translation checks run automatically in CI:
# In package.json "release" script
yarn check:translations && yarn check:locale-consistency && yarn testThis ensures:
- No hardcoded strings sneak into components
- Hungarian variants stay in sync
- All translation tests pass
Related
- User Guide: Localization - End-user documentation
- Testing - General testing guide
- Contributing - Contributing guidelines
