230 lines
6.7 KiB
Markdown
230 lines
6.7 KiB
Markdown
---
|
||
title: I18n Faz 9 — Hardening, Migrasyon ve Release Readiness
|
||
description: Çok dilli sistemin self-host production yayınına hazır olduğunu gösteren migration, backup, API, Dokploy ve operasyon raporu.
|
||
phase: 9
|
||
status: completed
|
||
last_updated: 2026-07-19
|
||
---
|
||
|
||
# I18n Faz 9 — Hardening, migrasyon ve release readiness
|
||
|
||
Faz-9, çok dilli sistemin production self-host kurulumunda güvenli şekilde
|
||
yayınlanabilmesi için migration, backup/restore, import, mobil API ve operasyon
|
||
kapılarını kapatır.
|
||
|
||
## Sonuç
|
||
|
||
Çok dilli sistem release adayıdır.
|
||
|
||
- Yeni kurulumda `tr` ve `en` aktif gelir, default locale `tr` olur.
|
||
- Eski Türkçe kolonlar `content_translations` içine `tr` locale'iyle backfill
|
||
edilir.
|
||
- Backfill tekrar çalışan migration'da duplicate üretmez.
|
||
- `fr` custom locale, content translation, backup ve restore zincirinde korunur.
|
||
- API v1 localization sözleşmesi additive ve eski mobil client'larla uyumludur.
|
||
- Runtime Supabase bağımlılığı veya Supabase env kullanımı geri gelmemiştir.
|
||
- Built-in kataloglar standalone server bundle içinde bulunur.
|
||
|
||
## Release kapıları
|
||
|
||
### Migration
|
||
|
||
`pnpm i18n:phase9-hardening` şu migration senaryolarını gerçek temp SQLite DB
|
||
üzerinde çalıştırır:
|
||
|
||
1. Boş DB migration.
|
||
2. Boş DB üzerinde ikinci migration çalıştırması.
|
||
3. 0000–0007 arası eski schema üzerinde production-benzeri Türkçe veri.
|
||
4. 0008 i18n migration'ı sonrası Türkçe backfill doğrulaması.
|
||
5. Aynı DB üzerinde tekrar migration ve idempotent count kontrolü.
|
||
|
||
Backfill doğrulanan alanlar:
|
||
|
||
- `project.name`
|
||
- `project.description`
|
||
- `task.title`
|
||
- `planning_section.title`
|
||
- `branding.portalWelcome`
|
||
|
||
### Backup ve restore
|
||
|
||
Hardening smoke, production-benzeri DB'ye `fr` custom locale ve Fransızca proje
|
||
çevirisi ekler, backup alır ve ayrı bir data directory içine restore eder.
|
||
Restore sonrası:
|
||
|
||
- `instance_locales.fr` aktif kalır.
|
||
- `content_translations` içindeki Fransızca çeviri korunur.
|
||
- `foreign_key_check` temiz döner.
|
||
|
||
Restore failure prosedürü de test edilir: checksum'u bozulan backup restore
|
||
edilmez ve mevcut hedef DB korunur.
|
||
|
||
### Supabase import uyumu
|
||
|
||
Faz-9, Supabase import aracının Faz-8 smoke kapsamını release blocker kabul eder.
|
||
`phase8:import-smoke` şu davranışları doğrular:
|
||
|
||
- dry-run DB'yi değiştirmez;
|
||
- apply import kayıtları taşır;
|
||
- `--allow-existing` idempotent upsert yapar;
|
||
- invalid enum, eksik foreign key ve unsafe storage path reddedilir;
|
||
- pre-import backup restore ile rollback provası yapılır.
|
||
|
||
Import aracı Supabase SDK kullanmaz; offline `neta-supabase-export` bundle'ını
|
||
okur.
|
||
|
||
## Dokploy / Docker operasyon notu
|
||
|
||
Dokploy'da `/app/data` kalıcı volume olmalıdır. Bu volume şunları taşır:
|
||
|
||
- `neta.db`
|
||
- `uploads/`
|
||
- `backups/`
|
||
- `tmp/`
|
||
|
||
Production deploy öncesi:
|
||
|
||
```bash
|
||
pnpm db:backup -- --retention-count 14
|
||
```
|
||
|
||
Restore provası production volume üstüne değil, ayrı geçici hedefe yapılmalıdır:
|
||
|
||
```bash
|
||
pnpm db:restore -- \
|
||
--from /app/data/backups/neta-TIMESTAMP \
|
||
--target /tmp/neta-restore-rehearsal \
|
||
--force
|
||
```
|
||
|
||
Translation tabloları `neta.db` içinde olduğu için backup/restore kapsamında
|
||
otomatik korunur:
|
||
|
||
- `instance_locales`
|
||
- `instance_i18n_settings`
|
||
- `instance_ui_translations`
|
||
- `content_translations`
|
||
- `clients.portal_locale`
|
||
- `portal_invitations.locale`
|
||
- `user_preferences.language`
|
||
|
||
## Self-host default locale
|
||
|
||
Yeni kurulumda default locale `tr` olur. Owner Ayarlar üzerinden varsayılan dili
|
||
aktif başka bir locale'e alabilir. Default locale yalnız `active` locale olabilir;
|
||
draft veya archived locale default yapılamaz.
|
||
|
||
Mobil istemciler instance dil bilgisini şu endpoint'lerden keşfeder:
|
||
|
||
- `GET /.well-known/neta`
|
||
- `GET /api/v1/meta`
|
||
- `GET /api/v1/me`
|
||
|
||
## Dil ekleme ve import/export rehberi
|
||
|
||
Owner akışı:
|
||
|
||
1. Ayarlar → Diller ve çeviriler bölümünden yeni locale ekle.
|
||
2. Locale'i önce `draft` olarak tut.
|
||
3. UI çeviri eksiklerini tamamla veya JSON import ile yükle.
|
||
4. İçerik formlarındaki locale tab'larında proje/görev/portal metinlerini gir.
|
||
5. Portal'a hazır olduğunda locale'i `active` yap.
|
||
6. Gerekirse varsayılan locale'i değiştir.
|
||
|
||
Import/export:
|
||
|
||
- UI çeviri export paketi `neta-i18n` formatındadır.
|
||
- Import yalnız owner scope ile çalışır.
|
||
- Built-in locale'ler silinmez veya archived yapılamaz.
|
||
- Bozuk namespace/key/value import'u validation ile reddedilir.
|
||
|
||
## Portal müşterisine dil atama
|
||
|
||
Müşteri portal dili iki noktada belirlenir:
|
||
|
||
1. Portal daveti oluştururken seçilen locale.
|
||
2. Müşteri detayındaki portal dili ayarı.
|
||
|
||
Davet kabul edildiğinde:
|
||
|
||
- `clients.portal_locale` set edilir;
|
||
- client user preference dili aynı locale'e alınır;
|
||
- davet kabulü sonrası login ekranı davet locale'iyle açılır.
|
||
|
||
## Mobil API localization sözleşmesi
|
||
|
||
Mobil taraf locale'i şu sırayla çözmelidir:
|
||
|
||
1. Explicit query: `/api/v1/me?locale=en`
|
||
2. Better Auth session preference
|
||
3. Client portal locale
|
||
4. `Accept-Language`
|
||
5. Instance default locale
|
||
|
||
Unsupported explicit locale için server:
|
||
|
||
```json
|
||
{
|
||
"ok": false,
|
||
"error": {
|
||
"code": "UNSUPPORTED_LOCALE",
|
||
"message": "Unsupported locale.",
|
||
"details": {
|
||
"messageKey": "validation.unsupportedLocale"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Client kullanıcıya gösterilecek metni kendi catalog'undan `messageKey` ile
|
||
çözmelidir; program akışı stabil `code` alanına bağlanmalıdır.
|
||
|
||
## Gözlem ve performans
|
||
|
||
Faz-9 smoke seviyesi performans kapıları:
|
||
|
||
- Dashboard ve portal listeleri batch content resolver kullanır; N+1 translation
|
||
sorgusu release riski kabul edilir.
|
||
- Missing translation durumunda raw key production kullanıcı arayüzüne düşmemeli;
|
||
fallback chain kullanılmalıdır.
|
||
- Catalog version, UI translation mutation'larında artar ve mobil meta cache
|
||
invalidation için kullanılabilir.
|
||
- Hassas içerik loglanmamalıdır; missing translation log'u key/locale seviyesinde
|
||
kalmalıdır.
|
||
|
||
## Verification
|
||
|
||
Çalıştırılan komutlar:
|
||
|
||
```bash
|
||
pnpm phase-i18n:boundary
|
||
pnpm i18n:phase9-hardening
|
||
pnpm i18n:phase8-smoke
|
||
pnpm i18n:phase7-smoke
|
||
pnpm i18n:phase5-smoke
|
||
pnpm i18n:phase3-smoke
|
||
pnpm i18n:phase2-smoke
|
||
pnpm i18n:phase1-smoke
|
||
pnpm phase8:import-smoke
|
||
pnpm typecheck
|
||
pnpm build
|
||
pnpm phase9:smoke
|
||
git diff --check
|
||
```
|
||
|
||
`phase9:smoke` local listener açtığı için sandbox içinde `EPERM` alabilir; bu
|
||
durumda izinli çalıştırılmalıdır.
|
||
|
||
## Release uyarısı
|
||
|
||
Bu rapor teknik release readiness sağlar. Gerçek production cutover için ortam
|
||
sahibi yine şu dış adımları yürütmelidir:
|
||
|
||
- production backup;
|
||
- maintenance/read-only penceresi;
|
||
- final Supabase export/import;
|
||
- gerçek satır ve dosya checksum karşılaştırması;
|
||
- owner/client kabul smoke'u;
|
||
- DNS/reverse proxy geçişi;
|
||
- rollback retention penceresi.
|