- Implement PKCE (Proof Key for Code Exchange) with S256 method - Add crypto/pkce module with code verifier and challenge generation - Modify OAuth flow to include code_challenge in authorization requests - Update HandleCallback to validate code_verifier during token exchange - Extend session lifetime from 7 to 30 days - Add comprehensive unit tests for PKCE functions - Maintain backward compatibility with fallback for non-PKCE sessions - Add detailed logging for OAuth flow with PKCE tracking PKCE enhances security by preventing authorization code interception attacks, as recommended by OAuth 2.1 and OIDC standards. feat: add encrypted refresh token storage with automatic cleanup - Add oauth_sessions table for storing encrypted refresh tokens - Implement AES-256-GCM encryption for refresh tokens using cookie secret - Create OAuth session repository with full CRUD operations - Add SessionWorker for automatic cleanup of expired sessions - Configure cleanup to run every 24h for sessions older than 37 days - Modify OAuth flow to store refresh tokens after successful authentication - Track client IP and user agent for session security validation - Link OAuth sessions to user sessions via session ID - Add comprehensive encryption tests with security validations - Integrate SessionWorker into server lifecycle with graceful shutdown This enables persistent OAuth sessions with secure token storage, reducing the need for frequent re-authentication from 7 to 30 days.
5.3 KiB
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:
- localStorage - Saved user choice
- navigator.language - Browser language
- 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
- Create translation file:
cd webapp/src/locales
cp en.json pt.json # Portuguese
- Translate:
{
"home": {
"title": "Ackify - Prova de Leitura",
"subtitle": "Assinaturas criptográficas de leitura"
}
}
- 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
}
})
- Add to selector:
<!-- components/LanguageSwitcher.vue -->
<button @click="changeLocale('pt')">🇵🇹 PT</button>
Backend
- Create directory:
mkdir -p backend/templates/emails/pt
- Create templates:
cp backend/templates/emails/en/reminder.html backend/templates/emails/pt/
cp backend/templates/emails/en/reminder.txt backend/templates/emails/pt/
-
Translate templates
-
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:
This file contains:
- Complete vue-i18n architecture
- Contribution guide
- Synchronization scripts
- Advanced examples