Skip to content

Latest commit

 

History

History
320 lines (238 loc) · 5.28 KB

File metadata and controls

320 lines (238 loc) · 5.28 KB

Internationalization (i18n)

Complete multilingual support for Ackify frontend.

Supported Languages

  • 🇫🇷 Français (default)
  • 🇬🇧 English (fallback)
  • 🇪🇸 Español
  • 🇩🇪 Deutsch
  • 🇮🇹 Italiano

Frontend (Vue.js)

Language Selection

The frontend automatically detects language via:

  1. localStorage - Saved user choice
  2. navigator.language - Browser language
  3. Fallback - English if not supported

Language Switcher

User interface with Unicode flags:

🇫🇷 FR | 🇬🇧 EN | 🇪🇸 ES | 🇩🇪 DE | 🇮🇹 IT

Click → Language changes + saves to localStorage

Translation Files

Located in /webapp/src/locales/:

locales/
├── fr.json    # Français
├── en.json    # English
├── es.json    # Español
├── de.json    # Deutsch
└── it.json    # Italiano

JSON Structure

{
  "home": {
    "title": "Ackify - Proof of Read",
    "subtitle": "Cryptographic read signatures"
  },
  "document": {
    "sign": "Sign this document",
    "signed": "Document signed",
    "signatures": "{count} confirmation | {count} confirmations"
  }
}

Pluralization:

"signatures": "{count} confirmation | {count} confirmations"

Usage:

{{ $t('document.signatures', { count: 42 }) }}
// → "42 confirmations"

Backend (Go)

Email Templates

Emails use multilingual templates in /backend/templates/emails/:

templates/emails/
├── fr/
│   ├── reminder.html
│   └── reminder.txt
├── en/
│   ├── reminder.html
│   └── reminder.txt
├── es/...
├── de/...
└── it/...

Sending with Locale

POST /api/v1/admin/documents/doc_id/reminders
Content-Type: application/json

{
  "emails": ["user@company.com"],
  "locale": "fr"
}

Backend loads template fr/reminder.html.

Configuration

# Default language for emails (default: en)
ACKIFY_MAIL_DEFAULT_LOCALE=fr

Adding a Language

Frontend

  1. Create translation file:
cd webapp/src/locales
cp en.json pt.json  # Portuguese
  1. Translate:
{
  "home": {
    "title": "Ackify - Prova de Leitura",
    "subtitle": "Assinaturas criptográficas de leitura"
  }
}
  1. Register in i18n:
// webapp/src/i18n.ts
import pt from './locales/pt.json'

const i18n = createI18n({
  locale: 'fr',
  fallbackLocale: 'en',
  messages: {
    fr, en, es, de, it,
    pt  // Add here
  }
})
  1. Add to selector:
<!-- components/LanguageSwitcher.vue -->
<button @click="changeLocale('pt')">🇵🇹 PT</button>

Backend

  1. Create directory:
mkdir -p backend/templates/emails/pt
  1. Create templates:
cp backend/templates/emails/en/reminder.html backend/templates/emails/pt/
cp backend/templates/emails/en/reminder.txt backend/templates/emails/pt/
  1. Translate templates

  2. Rebuild:

docker compose up -d --force-recreate ackify-ce --build

i18n Verification

Validation Script

Project includes a script to verify translation completeness:

cd webapp
npm run lint:i18n

Output:

✅ fr.json - 156 keys
✅ en.json - 156 keys
✅ es.json - 156 keys
✅ de.json - 156 keys
✅ it.json - 156 keys

All translations are complete!

CI/CD

Script automatically runs in GitHub Actions to block PRs with missing translations.

Best Practices

Translation Keys

  • ✅ Use structured keys: feature.action.label
  • ✅ Group by page/component
  • ✅ Avoid too generic keys (button, title)
  • ✅ Use placeholders: {count}, {name}

Example:

{
  "admin": {
    "documents": {
      "list": {
        "title": "Document list",
        "count": "{count} document | {count} documents"
      }
    }
  }
}

Synchronization

When adding new keys in French:

# Sync script (to create)
node scripts/sync-i18n-from-fr.js

Automatically copies new keys to other languages with [TODO].

Long Texts

For long texts, use arrays:

{
  "help": {
    "intro": [
      "Ackify allows creating cryptographic signatures.",
      "Each signature is timestamped and non-repudiable.",
      "Data is stored immutably."
    ]
  }
}

Usage:

<p v-for="line in $tm('help.intro')" :key="line">
  {{ line }}
</p>

Specific Formats

Dates

// Format with current locale
import { useI18n } from 'vue-i18n'

const { locale } = useI18n()
const formatted = new Date().toLocaleDateString(locale.value, {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
})
// fr: "15 janvier 2025"
// en: "January 15, 2025"

Numbers

const formatted = (42000).toLocaleString(locale.value)
// fr: "42 000"
// en: "42,000"

SEO & Meta Tags

Meta tags are dynamically translated:

<script setup>
import { useI18n } from 'vue-i18n'
import { useHead } from '@vueuse/head'

const { t } = useI18n()

useHead({
  title: t('home.title'),
  meta: [
    { name: 'description', content: t('home.description') }
  ]
})
</script>

Complete Documentation

For more details on frontend i18n implementation, see:

webapp/I18N.md

This file contains:

  • Complete vue-i18n architecture
  • Contribution guide
  • Synchronization scripts
  • Advanced examples