docs: add multilingual i18n rollout plan
This commit is contained in:
@@ -0,0 +1,171 @@
|
|||||||
|
---
|
||||||
|
title: Faz 0 ADR Seti
|
||||||
|
description: Neta cok dilli sisteminin baslangic karar kayitlari.
|
||||||
|
phase: 0
|
||||||
|
status: accepted
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 0 ADR Seti
|
||||||
|
|
||||||
|
Bu dosya cok dillilik calismasinin ilk uygulama kararlarini kilitler. Amac,
|
||||||
|
sonraki fazlarda ayni problemi tekrar tekrar tartismadan ilerleyebilmek ve
|
||||||
|
SQLite tabanli self-host hedefini korumaktir.
|
||||||
|
|
||||||
|
## ADR-I18N-0001 — UI cevirisi ve domain icerik cevirisi ayrilacak
|
||||||
|
|
||||||
|
### Durum
|
||||||
|
|
||||||
|
Kabul edildi.
|
||||||
|
|
||||||
|
### Baglam
|
||||||
|
|
||||||
|
Neta'da iki farkli metin tipi var:
|
||||||
|
|
||||||
|
- Uygulamanin kendisine ait sabit arayuz metinleri.
|
||||||
|
- Freelancer veya musteri tarafindan uretilen is icerikleri.
|
||||||
|
|
||||||
|
Bu iki veri tipini ayni tabloda veya ayni anahtar modeliyle tutmak, hem fallback
|
||||||
|
hem de izin modeli tarafinda karmasa yaratir.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
Arayuz cevirileri stabil katalog anahtarlariyla yonetilecek. Built-in `tr` ve
|
||||||
|
`en` kataloglari kod icinde surumlenecek; instance sahibi tarafindan eklenen
|
||||||
|
override'lar SQLite icindeki `instance_ui_translations` modelinde tutulacak.
|
||||||
|
|
||||||
|
Domain icerik cevirileri entity ve field bazli tutulacak. Bunun icin Faz-1'de
|
||||||
|
`content_translations` modeli eklenecek.
|
||||||
|
|
||||||
|
### Sonuc
|
||||||
|
|
||||||
|
- Arayuz metinleri release sureciyle denetlenebilir kalir.
|
||||||
|
- Sonradan eklenen diller deployment gerektirmeden doldurulabilir.
|
||||||
|
- Is icerikleri icin form tab'lari net bicimde modellenir.
|
||||||
|
- Chat, gunluk notu ve musteri mesajlari gibi iletisim icerikleri otomatik
|
||||||
|
cevrilmez; yazildigi dilde kalir.
|
||||||
|
|
||||||
|
## ADR-I18N-0002 — Locale cozumleme onceligi
|
||||||
|
|
||||||
|
### Durum
|
||||||
|
|
||||||
|
Kabul edildi.
|
||||||
|
|
||||||
|
### Baglam
|
||||||
|
|
||||||
|
Ayni instance icinde freelancer, musteri portali, davet ekranlari ve public API
|
||||||
|
farkli baglamlarda calisir. Locale cozumleme her yerde ayni kaynaga bakarsa
|
||||||
|
portal daveti gibi auth oncesi akislarda yanlis dil gorunebilir.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
Locale asagidaki sirayla cozumlenecek.
|
||||||
|
|
||||||
|
Auth oncesi davet ekranlari:
|
||||||
|
|
||||||
|
1. Davet kaydindaki locale.
|
||||||
|
2. Guvenli locale cookie'si.
|
||||||
|
3. Instance varsayilan dili.
|
||||||
|
4. Built-in fallback `tr`.
|
||||||
|
|
||||||
|
Freelancer uygulamasi:
|
||||||
|
|
||||||
|
1. Oturum acmis kullanicinin `user_preferences.language` degeri.
|
||||||
|
2. Guvenli locale cookie'si.
|
||||||
|
3. Instance varsayilan dili.
|
||||||
|
4. Built-in fallback `tr`.
|
||||||
|
|
||||||
|
Musteri portali:
|
||||||
|
|
||||||
|
1. Musteri profilinin veya `clients.portal_locale` alaninin locale degeri.
|
||||||
|
2. Portal davetinden gelen locale.
|
||||||
|
3. Guvenli locale cookie'si.
|
||||||
|
4. Instance varsayilan dili.
|
||||||
|
5. Built-in fallback `tr`.
|
||||||
|
|
||||||
|
Mobil ve API istekleri:
|
||||||
|
|
||||||
|
1. Auth kullanicisinin kayitli tercihi.
|
||||||
|
2. Guvenilir endpoint parametresi veya `Accept-Language` sinyali.
|
||||||
|
3. Instance varsayilan dili.
|
||||||
|
4. Built-in fallback `tr`.
|
||||||
|
|
||||||
|
### Sonuc
|
||||||
|
|
||||||
|
- Musteri davet ekrani login olmadan dogru dilde acilir.
|
||||||
|
- Freelancer tercihi musteri portalini yanlislikla etkilemez.
|
||||||
|
- Mobil istemci ayni kurallari API kontratindan okuyabilir.
|
||||||
|
|
||||||
|
## ADR-I18N-0003 — Dashboard ve portal route'larina locale prefix eklenmeyecek
|
||||||
|
|
||||||
|
### Durum
|
||||||
|
|
||||||
|
Kabul edildi.
|
||||||
|
|
||||||
|
### Baglam
|
||||||
|
|
||||||
|
`/tr/dashboard`, `/en/dashboard` gibi route prefix'leri public marketing
|
||||||
|
sitelerinde gucludur, ancak Neta'nin dashboard ve portal yapisi oturum, davet,
|
||||||
|
yetki ve self-host basitligi uzerine kurulu. Prefix eklemek route sayisini,
|
||||||
|
redirect davranislarini ve mobil endpoint eslemesini gereksiz buyutur.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
Dashboard, portal ve davet route'lari mevcut URL yapisini koruyacak. Dil, locale
|
||||||
|
resolver ve cookie/user/client kayitlariyla belirlenecek.
|
||||||
|
|
||||||
|
### Sonuc
|
||||||
|
|
||||||
|
- Var olan internal linkler ve mobil baglanti modeli korunur.
|
||||||
|
- Self-host deploy ve reverse proxy ayarlari sade kalir.
|
||||||
|
- Public landing/docs tarafi isterse ileride ayri route prefix stratejisi
|
||||||
|
kullanabilir; bu karar sadece uygulama kabugu icindir.
|
||||||
|
|
||||||
|
## ADR-I18N-0004 — Hafif custom runtime i18n katmani yazilacak
|
||||||
|
|
||||||
|
### Durum
|
||||||
|
|
||||||
|
Kabul edildi.
|
||||||
|
|
||||||
|
### Baglam
|
||||||
|
|
||||||
|
Projenin ana hedeflerinden biri dependency sayisini azaltmak ve self-host'u
|
||||||
|
kolaylastirmak. Mevcut ihtiyaclar temel katalog cozumleme, fallback, namespace
|
||||||
|
yukleme, `Intl` bicimlendirme ve SQLite override okuma uzerinden karsilanabilir.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
Ilk cok dillilik surumunde agir bir i18n runtime dependency'si eklenmeyecek.
|
||||||
|
Neta icinde kucuk bir i18n katmani yazilacak:
|
||||||
|
|
||||||
|
- `lib/i18n/*` ortak tip ve format yardimcilari.
|
||||||
|
- `server/i18n/*` locale resolver, katalog merge ve translator servisleri.
|
||||||
|
- Client component'ler icin sinirli scope'ta provider.
|
||||||
|
- Tarih, sayi ve para bicimlendirme icin native `Intl`.
|
||||||
|
|
||||||
|
### Sonuc
|
||||||
|
|
||||||
|
- Yeni dependency eklenmeden Faz-2 runtime kurulabilir.
|
||||||
|
- Katalog sozlesmesi Neta'nin domain ihtiyaclarina gore sekillenir.
|
||||||
|
- Ileride ihtiyac buyurse baska kutuphaneye gecis icin katalog anahtarlari
|
||||||
|
korunabilir.
|
||||||
|
|
||||||
|
## ADR-I18N-0005 — Built-in diller ve yayin kurali
|
||||||
|
|
||||||
|
### Durum
|
||||||
|
|
||||||
|
Kabul edildi.
|
||||||
|
|
||||||
|
### Karar
|
||||||
|
|
||||||
|
Turkce (`tr`) ve Ingilizce (`en`) built-in, aktif ve silinemez diller olacak.
|
||||||
|
Instance varsayilan dili ilk migration sonrasinda `tr` olacak.
|
||||||
|
|
||||||
|
Yeni eklenen diller once `draft` durumunda olusturulacak. Portal icin kritik
|
||||||
|
namespace'leri tamamlanmayan bir dil musteri dili olarak secilemeyecek.
|
||||||
|
|
||||||
|
### Sonuc
|
||||||
|
|
||||||
|
- Eksik ceviri musteriye yarim bir portal deneyimi olarak yansimaz.
|
||||||
|
- Instance sahibi yeni dili hazirlayip test ettikten sonra aktif edebilir.
|
||||||
|
- Fallback davranisi her zaman tahmin edilebilir kalir.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
title: Faz 0 Baseline Sonuclari
|
||||||
|
description: Cok dillilik calismasi baslamadan onceki build, typecheck ve smoke referansi.
|
||||||
|
phase: 0
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 0 Baseline Sonuclari
|
||||||
|
|
||||||
|
Bu baseline, cok dillilik kod degisikliklerinden onceki saglik referansidir.
|
||||||
|
Sonraki fazlarda yeni hata olup olmadigini anlamak icin bu komut setiyle
|
||||||
|
karsilastirma yapilacak.
|
||||||
|
|
||||||
|
## Komut sonuclari
|
||||||
|
|
||||||
|
| Komut | Sonuc | Not |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `pnpm typecheck` | Basarili | `tsc --noEmit` hatasiz tamamlandi. |
|
||||||
|
| `pnpm build` | Basarili | Next.js 16.2.10 production build ve standalone hazirlama tamamlandi. |
|
||||||
|
| `pnpm phase9:smoke` | Basarili | API boundary, auth/invitation ve mobile API smoke kapilari gecti. |
|
||||||
|
|
||||||
|
## Build notu
|
||||||
|
|
||||||
|
`pnpm build` sirasinda su Next.js uyarisi goruldu:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Using edge runtime on a page currently disables static generation for that page
|
||||||
|
```
|
||||||
|
|
||||||
|
Bu Faz-0 icin yeni bir hata degil; mevcut route/runtime tercihinden kaynaklanan
|
||||||
|
uyari olarak kaydedildi.
|
||||||
|
|
||||||
|
## Faz-1 icin baseline beklentisi
|
||||||
|
|
||||||
|
Faz-1 sonunda en az asagidaki komutlar tekrar calistirilmelidir:
|
||||||
|
|
||||||
|
- `pnpm typecheck`
|
||||||
|
- `pnpm build`
|
||||||
|
- Locale migration eklendikten sonra ilgili DB smoke komutu
|
||||||
|
- Varsa yeni `i18n` service unit/smoke komutu
|
||||||
|
|
||||||
|
Faz-5 ve Faz-6 gibi domain/portal davranisi degisen fazlarda `phase6:smoke` ve
|
||||||
|
portal-specific fixture testleri de baseline setine eklenmelidir.
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
---
|
||||||
|
title: Faz 0 I18n Fixture Plani
|
||||||
|
description: Cok dillilik migration, katalog ve portal testleri icin seed senaryolari.
|
||||||
|
phase: 0
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 0 I18n Fixture Plani
|
||||||
|
|
||||||
|
Bu plan Faz-1 sonrasinda script veya test fixture'ina donusturulecek veri setini
|
||||||
|
tanımlar. Hedef, locale resolver, fallback, domain icerik cevirileri ve portal
|
||||||
|
dili davranisini ayni senaryoda dogrulamaktir.
|
||||||
|
|
||||||
|
## Locale fixture'lari
|
||||||
|
|
||||||
|
| Locale | Durum | Built-in | Fallback | Amac |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `tr` | `active` | Evet | Yok | Instance default ve mevcut verinin ana dili. |
|
||||||
|
| `en` | `active` | Evet | `tr` | Built-in ikinci dil ve portal client testi. |
|
||||||
|
| `fr` | `draft` | Hayir | `en` | Eksik katalog/fallback testi. |
|
||||||
|
| `ar-XB` | `test` | Hayir | `en` | RTL smoke ve layout kontrolu. |
|
||||||
|
|
||||||
|
`tr` ve `en` silinemez. `fr` portal dili olarak ancak kritik portal namespace'leri
|
||||||
|
tamamlaninca secilebilir. `ar-XB` urun ayarlarinda normal kullaniciya
|
||||||
|
gosterilmez.
|
||||||
|
|
||||||
|
## Katalog fixture'lari
|
||||||
|
|
||||||
|
### Turkce built-in katalog
|
||||||
|
|
||||||
|
Tum namespace'lerde eksiksiz olmalidir:
|
||||||
|
|
||||||
|
- `common`
|
||||||
|
- `auth`
|
||||||
|
- `navigation`
|
||||||
|
- `dashboard`
|
||||||
|
- `clients`
|
||||||
|
- `projects`
|
||||||
|
- `tasks`
|
||||||
|
- `calendar`
|
||||||
|
- `finance`
|
||||||
|
- `journal`
|
||||||
|
- `chat`
|
||||||
|
- `settings`
|
||||||
|
- `portal`
|
||||||
|
- `status`
|
||||||
|
- `validation`
|
||||||
|
- `api`
|
||||||
|
|
||||||
|
### Ingilizce built-in katalog
|
||||||
|
|
||||||
|
Turkce ile ayni anahtar setine sahip olmalidir. Faz-2 testinde anahtar farki
|
||||||
|
release engeli kabul edilir.
|
||||||
|
|
||||||
|
### Eksik Fransizca katalog
|
||||||
|
|
||||||
|
Fallback test etmek icin bilincli olarak kismi tutulur.
|
||||||
|
|
||||||
|
Ornek:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"navigation.dashboard": "Tableau de bord",
|
||||||
|
"navigation.projects": "Projets",
|
||||||
|
"portal.dashboard.title": "Apercu",
|
||||||
|
"common.save": "Enregistrer"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Beklenen davranis: Eksik `portal.tasks.title` gibi anahtarlarda once `en`, sonra
|
||||||
|
`tr` fallback metni gosterilir; kullaniciya ham anahtar basılmaz.
|
||||||
|
|
||||||
|
## Auth fixture'lari
|
||||||
|
|
||||||
|
| Kullanici | Rol | Email | Dil | Baglam |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| Freelancer | `admin` | `owner@neta.local` | `tr` | Dashboard, ayarlar ve ceviri yonetimi. |
|
||||||
|
| Turkce musteri | `client` | `client-tr@neta.local` | `tr` | Portal default dil testi. |
|
||||||
|
| Ingilizce musteri | `client` | `client-en@neta.local` | `en` | Portal client locale testi. |
|
||||||
|
|
||||||
|
Freelancer hesabi instance default dilini `tr` olarak korur. Ingilizce musteri
|
||||||
|
icin davet kaydinda ve `clients.portal_locale` alaninda `en` beklenir.
|
||||||
|
|
||||||
|
## Domain veri fixture'i
|
||||||
|
|
||||||
|
### Musteriler
|
||||||
|
|
||||||
|
| ID | Ad | Portal dili | Not |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `client-tr` | Ada Yilmaz | `tr` | Turkce portal goruntusu. |
|
||||||
|
| `client-en` | Nova Studio | `en` | Ingilizce portal goruntusu. |
|
||||||
|
|
||||||
|
### Projeler
|
||||||
|
|
||||||
|
| ID | Musteri | Default baslik | EN baslik | FR baslik | Not |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `project-website` | `client-tr` | Web sitesi yenileme | Website refresh | Bos | Fallback testi. |
|
||||||
|
| `project-brand` | `client-en` | Marka kiti | Brand kit | Kit de marque | Portal EN ve FR content testi. |
|
||||||
|
|
||||||
|
Beklenen davranis:
|
||||||
|
|
||||||
|
- `tr` isteyen kullanici default kolon veya `content_translations(tr)` degerini
|
||||||
|
gorur.
|
||||||
|
- `en` isteyen kullanici EN content translation'i gorur.
|
||||||
|
- `fr` isteyen kullanici FR alan bos ise fallback zinciriyle EN veya TR gorur.
|
||||||
|
|
||||||
|
### Gorevler
|
||||||
|
|
||||||
|
| ID | Proje | Public | TR baslik | EN baslik |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `task-wireframe` | `project-website` | Evet | Ana sayfa wireframe | Homepage wireframe |
|
||||||
|
| `task-internal` | `project-website` | Hayir | Ic notlari toparla | Gather internal notes |
|
||||||
|
|
||||||
|
Portal yalnizca `is_public_to_client = true` gorevleri gosterir.
|
||||||
|
|
||||||
|
### Planlama bolumleri
|
||||||
|
|
||||||
|
| ID | Proje | Kategori | TR baslik | EN baslik |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `plan-scope` | `project-website` | `scope` | Kapsam | Scope |
|
||||||
|
| `plan-delivery` | `project-website` | `delivery` | Teslimat | Delivery |
|
||||||
|
|
||||||
|
Kategori teknik degeri cevrilmez; label `status` veya `projects` namespace'inden
|
||||||
|
gelir.
|
||||||
|
|
||||||
|
## Portal davet fixture'lari
|
||||||
|
|
||||||
|
| Davet | Email | Locale | Beklenen ekran dili |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `invite-tr` | `client-tr@neta.local` | `tr` | Turkce |
|
||||||
|
| `invite-en` | `client-en@neta.local` | `en` | Ingilizce |
|
||||||
|
| `invite-fr-draft` | `client-en@neta.local` | `fr` | Portal publish kurali nedeniyle reddedilir veya fallback EN kullanir. |
|
||||||
|
|
||||||
|
Davet ekranlari auth oncesi oldugu icin locale'i session'dan degil davet
|
||||||
|
kaydindan alir.
|
||||||
|
|
||||||
|
## API contract fixture'lari
|
||||||
|
|
||||||
|
`/api/v1/meta` Faz-8 sonunda en az su localization bilgisini dondurmelidir:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"localization": {
|
||||||
|
"defaultLocale": "tr",
|
||||||
|
"supportedLocales": [
|
||||||
|
{ "code": "tr", "name": "Turkce", "status": "active", "builtIn": true },
|
||||||
|
{ "code": "en", "name": "English", "status": "active", "builtIn": true },
|
||||||
|
{ "code": "fr", "name": "Francais", "status": "draft", "builtIn": false }
|
||||||
|
],
|
||||||
|
"fallbacks": {
|
||||||
|
"en": "tr",
|
||||||
|
"fr": "en"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Mobil istemci aktif olmayan dilleri kullanici seciminde gostermemeli, ancak
|
||||||
|
ceviri yonetimi ekranlari icin admin API'de draft dilleri gorebilmelidir.
|
||||||
|
|
||||||
|
## Smoke beklentileri
|
||||||
|
|
||||||
|
Faz-2 sonrasinda:
|
||||||
|
|
||||||
|
- `t("common.save")` `tr` icin `Kaydet`, `en` icin `Save` dondurur.
|
||||||
|
- `fr` locale'inde eksik anahtar ham key olarak gosterilmez.
|
||||||
|
- `formatMoney(10000, "TRY", "tr")` Turkce bicim verir.
|
||||||
|
- `formatDate(..., "en")` Ingilizce ay adi verir.
|
||||||
|
|
||||||
|
Faz-6 sonrasinda:
|
||||||
|
|
||||||
|
- `client-en` portali EN navigasyon ve EN proje basliklariyla acilir.
|
||||||
|
- `client-tr` portali TR kalir.
|
||||||
|
- Davet kabul edildikten sonra secilen locale client kaydina yansir.
|
||||||
|
|
||||||
|
Faz-8 sonrasinda:
|
||||||
|
|
||||||
|
- `/api/v1/meta` supported locale listesini dondurur.
|
||||||
|
- `/api/v1/me` kullanicinin etkin locale'ini ve fallback chain'ini dondurur.
|
||||||
|
- API hata response'lari stabil kod + locale'e uygun insan okunur mesaj tasir.
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
---
|
||||||
|
title: Faz 0 I18n Envanteri
|
||||||
|
description: Kullaniciya gorunen metinler, locale sabitleri ve namespace migration envanteri.
|
||||||
|
phase: 0
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 0 I18n Envanteri
|
||||||
|
|
||||||
|
Bu envanter Faz-1 ve Faz-2 icin kapsam sinirini belirler. Liste uygulama
|
||||||
|
kodunun mevcut durumuna gore hazirlandi; yeni sayfa eklendikce ayni namespace
|
||||||
|
modeliyle genisletilmelidir.
|
||||||
|
|
||||||
|
## Namespace standardi
|
||||||
|
|
||||||
|
Katalog anahtarlari `namespace.section.item` biciminde olacak. Turkce cumleler
|
||||||
|
anahtar olarak kullanilmayacak.
|
||||||
|
|
||||||
|
| Namespace | Sahip oldugu alan |
|
||||||
|
| --- | --- |
|
||||||
|
| `common` | Kaydet, iptal, sil, ara, filtrele, yukleniyor, bos durum, toast eylemleri |
|
||||||
|
| `auth` | Login, register, setup, davet kabul, auth hata mesajlari |
|
||||||
|
| `navigation` | Freelancer ve portal sidebar grup/link label'lari |
|
||||||
|
| `dashboard` | Freelancer ana sayfa basliklari, stats, grafik ve son kayit kartlari |
|
||||||
|
| `clients` | Musteri liste, detay, form, portal hesap islemleri |
|
||||||
|
| `projects` | Proje liste, detay, form, planlama, revizyon ve risk analizi |
|
||||||
|
| `tasks` | Gorev liste, kanban, form, durum ve oncelik UI metinleri |
|
||||||
|
| `calendar` | Takvim, etkinlik kartlari, etkinlik formu |
|
||||||
|
| `finance` | Finans stats, islem formu, kategori, AI analiz modal'i |
|
||||||
|
| `journal` | Gunluk liste, form, mood/enerji label'lari |
|
||||||
|
| `chat` | Sohbet listesi, bos durum, hata ve input metinleri |
|
||||||
|
| `settings` | Genel gorunum, marka, AI, profil, dil yonetimi |
|
||||||
|
| `portal` | Musteri portal dashboard, projeler, gorevler, revizyonlar |
|
||||||
|
| `status` | Enum label'lari: status, priority, type, pipeline, payment |
|
||||||
|
| `validation` | Form/action hata ve basari mesajlari |
|
||||||
|
| `api` | Mobil/public API hata kodlari icin insan okunur mesajlar |
|
||||||
|
|
||||||
|
## Freelancer migration listesi
|
||||||
|
|
||||||
|
Faz-4'te asagidaki sirayla UI sabitleri katalog anahtarina alinacak.
|
||||||
|
|
||||||
|
| Sira | Route veya dosya | Namespace'ler | Not |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | `config/sidebar.ts` | `navigation` | Sidebar link ve grup adlari merkezi baslangic noktasi. |
|
||||||
|
| 2 | `components/layout/app-shell.tsx` | `navigation`, `common`, `settings` | Account menu, tema ve layout metinleri. |
|
||||||
|
| 3 | `app/(dashboard)/page.tsx`, `dashboard-client.tsx` | `dashboard`, `common`, `status` | Ana stats, grafik bos durumlari ve tarih/para formatlari. |
|
||||||
|
| 4 | `app/(dashboard)/clients/*` | `clients`, `common`, `status`, `validation` | Liste, detay, portal hesabi, activity mesajlari. |
|
||||||
|
| 5 | `app/(dashboard)/projects/*` | `projects`, `common`, `status`, `validation` | Proje formu, planlama, revizyon ve risk analizi. |
|
||||||
|
| 6 | `app/(dashboard)/tasks/*` | `tasks`, `common`, `status`, `validation` | Liste/kanban ayrimi ve gorev action metinleri. |
|
||||||
|
| 7 | `app/(dashboard)/calendar/*` | `calendar`, `common`, `status` | Date formatting Faz-2 format yardimcilarina tasinir. |
|
||||||
|
| 8 | `app/(dashboard)/finance/*` | `finance`, `common`, `status`, `validation` | Para formatting ve AI modal metinleri. |
|
||||||
|
| 9 | `app/(dashboard)/journal/*` | `journal`, `common`, `validation` | Mood label'lari ve date formatter'lar. |
|
||||||
|
| 10 | `app/(dashboard)/chat/*`, `app/api/chat/route.ts` | `chat`, `api`, `validation` | Kullaniciya donen hata mesaji daha detayli hale getirilmisti; i18n anahtariyla baglanacak. |
|
||||||
|
| 11 | `app/(dashboard)/settings/*` | `settings`, `common`, `validation` | Dil yonetimi Faz-3'te eklenecegi icin en son genisletilir. |
|
||||||
|
| 12 | `app/(dashboard)/analytics/*`, `business/*` | `dashboard`, `finance`, `common` | Mevcut business ekranlari i18n kapsamina alinacak. |
|
||||||
|
|
||||||
|
## Portal migration listesi
|
||||||
|
|
||||||
|
Portal Faz-6'da musteri diliyle birlikte ele alinacak.
|
||||||
|
|
||||||
|
| Sira | Route veya dosya | Namespace'ler | Not |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 1 | `config/portal-sidebar.ts` | `navigation`, `portal` | Portal navigasyon label'lari. |
|
||||||
|
| 2 | `components/layout/portal-shell.tsx` | `portal`, `navigation`, `common` | Kullanici menu ve kabuk metinleri. |
|
||||||
|
| 3 | `app/portal/page.tsx` | `portal`, `dashboard`, `common` | Musteri dashboard stats ve bos durumlari. |
|
||||||
|
| 4 | `app/portal/projects/page.tsx` | `portal`, `projects`, `status` | Proje liste metinleri ve tarih formatlari. |
|
||||||
|
| 5 | `app/portal/projects/[id]/*` | `portal`, `projects`, `tasks`, `status`, `validation` | Cevrilebilir project/task content resolver kullanir. |
|
||||||
|
| 6 | `app/portal/tasks/page.tsx` | `portal`, `tasks`, `status` | Public gorev metinleri. |
|
||||||
|
| 7 | `app/portal/revisions/page.tsx` | `portal`, `projects`, `validation` | Revizyon talepleri yazildigi dilde kalir. |
|
||||||
|
| 8 | `app/invite/[token]/*` | `auth`, `portal`, `validation` | Auth oncesi davet locale'iyle render edilir. |
|
||||||
|
|
||||||
|
## Auth ve API migration listesi
|
||||||
|
|
||||||
|
| Alan | Dosya | Namespace | Not |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Admin setup | `app/register/page.tsx`, `app/login/*` | `auth`, `validation` | Cift toast fix'i korunarak metinler kataloglanir. |
|
||||||
|
| Better Auth route | `app/api/auth/[...all]/route.ts` | `api`, `auth` | Kullaniciya acik hata mapping'i gerekir. |
|
||||||
|
| Portal invitations API | `app/api/portal-invitations/*` | `api`, `portal` | Locale parametresi Faz-6'da kontrata eklenir. |
|
||||||
|
| Mobile API v1 | `app/api/v1/*`, `server/api/v1/*` | `api`, `common` | Meta endpoint supported/default locale bilgisini dondurur. |
|
||||||
|
| Branding API | `app/api/branding/*` | `api`, `settings` | Workspace gorunum metinleri ve mobile consumption ayni kalir. |
|
||||||
|
|
||||||
|
## Sabit locale kullanimlari
|
||||||
|
|
||||||
|
Asagidaki kullanimlar Faz-2 format yardimcilariyla degistirilecek.
|
||||||
|
|
||||||
|
| Kullanim | Dosyalar |
|
||||||
|
| --- | --- |
|
||||||
|
| `<html lang="tr">` | `app/layout.tsx` |
|
||||||
|
| `date-fns/locale/tr` | `app/portal/page.tsx`, `app/portal/projects/page.tsx`, `app/portal/tasks/page.tsx`, `app/portal/revisions/page.tsx`, `app/portal/projects/[id]/portal-project-client.tsx`, `app/(dashboard)/clients/clients-client.tsx`, `app/(dashboard)/clients/[id]/client-detail-client.tsx`, `app/(dashboard)/business/proposals/proposals-client.tsx`, `app/(dashboard)/business/invoices/invoices-client.tsx`, `app/(dashboard)/business/subscriptions/subscriptions-client.tsx` |
|
||||||
|
| `Intl.*("tr-TR")` | `dashboard-client.tsx`, `calendar-client.tsx`, `finance-client.tsx`, `journal-client.tsx`, `projects-client.tsx`, `project-detail-client.tsx`, `tasks-client.tsx`, business client'lari |
|
||||||
|
| `toLocaleDateString("tr-TR")` | `dashboard-client.tsx`, `project-detail-client.tsx` |
|
||||||
|
| `localeCompare(..., "tr")` | `app/(dashboard)/projects/page.tsx` |
|
||||||
|
|
||||||
|
## Kullaniciya gorunen sabit metin kaynaklari
|
||||||
|
|
||||||
|
Turkce karakter iceren veya dogrudan kullaniciya donen string barindirma ihtimali
|
||||||
|
en yuksek alanlar:
|
||||||
|
|
||||||
|
| Klasor | Kapsam |
|
||||||
|
| --- | --- |
|
||||||
|
| `app/(dashboard)` | Freelancer ekranlarinin buyuk bolumu. |
|
||||||
|
| `app/portal` | Musteri portali. |
|
||||||
|
| `app/invite` | Auth oncesi portal daveti. |
|
||||||
|
| `app/login`, `app/register` | Auth ekranlari. |
|
||||||
|
| `app/api` | Kullaniciya donen JSON hata/basari metinleri. |
|
||||||
|
| `components/layout` | Sidebar, account menu ve shell metinleri. |
|
||||||
|
| `components/system` | Ortak page header, stat card ve empty/error state metinleri. |
|
||||||
|
| `config/sidebar.ts` | Freelancer navigasyon metinleri. |
|
||||||
|
| `config/portal-sidebar.ts` | Portal navigasyon metinleri. |
|
||||||
|
| `server/auth`, `server/services`, `server/ai` | Server action/API hata ve toast mesajlari. |
|
||||||
|
|
||||||
|
## Cevrilebilir domain alan registry'si
|
||||||
|
|
||||||
|
Ilk surumde asagidaki alanlar `content_translations` ile locale bazli
|
||||||
|
cevrilebilir kabul edilir.
|
||||||
|
|
||||||
|
| Entity | Field | Zorunlu locale | Not |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `projects` | `name` | Instance default | Mevcut kolon backfill kaynagi. |
|
||||||
|
| `projects` | `description` | Opsiyonel | Portal ve freelancer detayinda kullanilir. |
|
||||||
|
| `projects` | `coverImageAlt` | Opsiyonel | A11y icin portalda onemli. |
|
||||||
|
| `tasks` | `title` | Instance default | Public/private fark etmeksizin ayni alan modeli. |
|
||||||
|
| `tasks` | `description` | Opsiyonel | Musteriye acik gorevlerde portalda gosterilir. |
|
||||||
|
| `calendar_events` | `title` | Instance default | Etkinlik ortak alandir, ileride portal visibility eklenirse hazir. |
|
||||||
|
| `calendar_events` | `description` | Opsiyonel | |
|
||||||
|
| `clients` | `notes` | Opsiyonel | Ilk surumde internal kalir; ceviri desteklenebilir ama portalda gosterilmez. |
|
||||||
|
| `planning_sections` | `title` | Instance default | Proje planlama bolumleri icin. |
|
||||||
|
| `planning_sections` | `content` | Opsiyonel | Rich text degilse plain text olarak saklanir. |
|
||||||
|
| `branding/settings` | `workspaceName` | Instance default | UI/portal basliklari ve mobile meta icin. |
|
||||||
|
| `branding/settings` | `metaTitle` | Instance default | Browser metadata. |
|
||||||
|
| `branding/settings` | `metaDescription` | Opsiyonel | SEO/public metadata. |
|
||||||
|
|
||||||
|
Ilk surumde cevrilmeyecek alanlar:
|
||||||
|
|
||||||
|
- Chat mesajlari.
|
||||||
|
- Revizyon talepleri.
|
||||||
|
- Gunluk notlari.
|
||||||
|
- Finans islem aciklamalari.
|
||||||
|
- Maliyet, tarih, para birimi, yuzde ve enum teknik degerleri.
|
||||||
|
|
||||||
|
## RTL test locale'i
|
||||||
|
|
||||||
|
Ilk release hedefi LTR diller olsa da layout kirilmasini erkenden gormek icin
|
||||||
|
test locale'i `ar-XB` olarak belirlendi.
|
||||||
|
|
||||||
|
Bu locale aktif urun dili olarak sunulmayacak. Faz-7'de `dir="rtl"` davranisini,
|
||||||
|
sidebar hizalamalarini, form tab'larini ve modal yerlesimlerini smoke etmek icin
|
||||||
|
fixture olarak kullanilacak.
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
title: Faz 0 Migration ve Geri Donus Sozlesmesi
|
||||||
|
description: Cok dillilik calismasi icin expand/migrate/contract sirasi ve backup kurallari.
|
||||||
|
phase: 0
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 0 Migration ve Geri Donus Sozlesmesi
|
||||||
|
|
||||||
|
Bu sozlesme Faz-1'den itibaren veritabani degisikliklerinin nasil yapilacagini
|
||||||
|
tanımlar. Neta self-host bir urun oldugu icin migration'lar veri kaybi riski
|
||||||
|
tasimamali ve rollback hikayesi basit kalmalidir.
|
||||||
|
|
||||||
|
## Uygulama sirasi
|
||||||
|
|
||||||
|
1. Expand
|
||||||
|
|
||||||
|
Yeni tablolar ve yeni nullable kolonlar eklenir. Mevcut kolonlar kaldirilmaz,
|
||||||
|
renamelenmez ve zorunlu hale getirilmez.
|
||||||
|
|
||||||
|
2. Backfill
|
||||||
|
|
||||||
|
Mevcut Turkce veriler instance default locale'i kabul edilerek
|
||||||
|
`content_translations` icine kopyalanir. Orijinal kolonlar okunabilir kalir.
|
||||||
|
|
||||||
|
3. Dual read
|
||||||
|
|
||||||
|
Okuma katmani once translation resolver'a bakar, eksikse mevcut kolona duser.
|
||||||
|
Bu asamada eski veriler ve yeni veriler ayni anda calisir.
|
||||||
|
|
||||||
|
4. Dual write
|
||||||
|
|
||||||
|
Formlar instance default locale alanini hem mevcut kolona hem translation
|
||||||
|
tablosuna yazar. Ek locale'ler sadece translation tablosuna yazilir.
|
||||||
|
|
||||||
|
5. Contract
|
||||||
|
|
||||||
|
Ancak en az bir release sonra eski kolonlarin kaldirilmasi veya tamamen internal
|
||||||
|
fallback haline getirilmesi tartisilir. Ilk cok dillilik fazlarinda contract
|
||||||
|
adimi yapilmayacak.
|
||||||
|
|
||||||
|
## Backup adimlari
|
||||||
|
|
||||||
|
Her schema migration oncesi:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm db:backup
|
||||||
|
```
|
||||||
|
|
||||||
|
Backup dosyasi deploy notuna yazilmalidir. Dokploy veya Docker deploy'da volume
|
||||||
|
path'i kontrol edilmeden migration calistirilmamalidir.
|
||||||
|
|
||||||
|
## Geri donus adimlari
|
||||||
|
|
||||||
|
Migration sonrasi kritik hata varsa:
|
||||||
|
|
||||||
|
1. Uygulama yeni surumden onceki image/commit'e geri alinir.
|
||||||
|
2. SQLite dosyasi backup'tan restore edilir.
|
||||||
|
3. `pnpm db:restore <backup-file>` veya host tarafindaki manuel restore adimi
|
||||||
|
kullanilir.
|
||||||
|
4. Restore sonrasi `pnpm phase9:smoke` ile health/API temel davranisi kontrol
|
||||||
|
edilir.
|
||||||
|
|
||||||
|
## Faz bazli DB dokunuslari
|
||||||
|
|
||||||
|
| Faz | DB degisikligi | Risk | Not |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Faz-1 | Locale ve translation tablolari, `clients.portal_locale`, `portal_invitations.locale`, preference check constraint genisletme | Orta | Expand only. |
|
||||||
|
| Faz-2 | DB yok veya yalnizca katalog version seed'i | Dusuk | Runtime/cache agirlikli. |
|
||||||
|
| Faz-3 | UI translation CRUD | Orta | Admin-only mutation ve audit gerekli. |
|
||||||
|
| Faz-5 | Domain content translations dual write | Yuksek | Backfill ve resolver testleri sart. |
|
||||||
|
| Faz-6 | Portal locale davet akisi | Orta | Auth oncesi davet dili kritik. |
|
||||||
|
| Faz-8 | API contract genisletme | Dusuk/Orta | Backward compatible response alanlari eklenir. |
|
||||||
|
|
||||||
|
## Non-goal
|
||||||
|
|
||||||
|
Ilk cok dillilik release'inde asagidakiler yapilmayacak:
|
||||||
|
|
||||||
|
- Mevcut `projects.name`, `tasks.title` gibi kolonlari kaldirmak.
|
||||||
|
- Otomatik makine cevirisi eklemek.
|
||||||
|
- Public route'lara locale prefix eklemek.
|
||||||
|
- Chat/gunluk/revizyon taleplerini otomatik cevirmek.
|
||||||
|
- Kullanici yazili icerigini farkli locale'e sessizce overwrite etmek.
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
title: Faz 1 Locale Veri Modeli ve Service Katmani
|
||||||
|
description: Cok dillilik icin SQLite semasi, seed, locale yonetimi servisi ve smoke sonuclari.
|
||||||
|
phase: 1
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 1 Locale Veri Modeli ve Service Katmani
|
||||||
|
|
||||||
|
Faz-1, cok dillilik icin runtime'a girmeden once gerekli kalici veri modelini
|
||||||
|
ve owner-only locale yonetimi kurallarini ekledi.
|
||||||
|
|
||||||
|
## Eklenen veri modeli
|
||||||
|
|
||||||
|
| Tablo veya kolon | Amac |
|
||||||
|
| --- | --- |
|
||||||
|
| `instance_locales` | Instance icindeki aktif, draft, archived ve test dillerini tutar. |
|
||||||
|
| `instance_i18n_settings` | Varsayilan locale ve katalog versiyonunu tutar. |
|
||||||
|
| `instance_ui_translations` | Built-in katalog ustune yazilacak instance UI cevirilerini tutar. |
|
||||||
|
| `content_translations` | Domain entity/field/locale bazli icerik cevirilerini tutar. |
|
||||||
|
| `clients.portal_locale` | Musteri portal dilini tutar. |
|
||||||
|
| `portal_invitations.locale` | Auth oncesi davet ekraninin dilini tutar. |
|
||||||
|
| `user_preferences.language` | Sabit `tr/en` constraint yerine 2-12 karakter locale kodu kabul eder. |
|
||||||
|
|
||||||
|
Migration `tr` ve `en` built-in locale'lerini seed eder, varsayilan instance
|
||||||
|
dilini `tr` olarak olusturur ve mevcut preference verisini kayipsiz tasir.
|
||||||
|
|
||||||
|
## Service kurallari
|
||||||
|
|
||||||
|
`I18nService` ile asagidaki kurallar kilitlendi:
|
||||||
|
|
||||||
|
- Locale yonetimi yalnizca freelancer/owner actor tarafindan yapilabilir.
|
||||||
|
- Disabled actor islem yapamaz.
|
||||||
|
- Locale kodu kisa BCP47 formatinda dogrulanir: `tr`, `en`, `fr`, `ar-XB`.
|
||||||
|
- `tr` ve `en` built-in, aktif ve arsivlenemez diller olarak korunur.
|
||||||
|
- Fallback locale mevcut ve arsivlenmemis olmak zorundadir.
|
||||||
|
- Locale kendi kendine fallback olamaz.
|
||||||
|
- Fallback zinciri dongu olusturamaz.
|
||||||
|
- Varsayilan dil yalnizca `active` locale olabilir.
|
||||||
|
- Default, fallback, user preference, client portal veya invitation tarafindan
|
||||||
|
kullanilan locale arsivlenemez.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Calistirilan komutlar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm i18n:phase1-smoke
|
||||||
|
pnpm build
|
||||||
|
pnpm phase9:smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
Sonuclar basarili. Build sirasinda mevcut Next edge runtime statik uretim uyarisi
|
||||||
|
tekrar goruldu; Faz-1 ile ilgili yeni bir hata degil.
|
||||||
|
|
||||||
|
`i18n:phase1-smoke` su kapilari kontrol eder:
|
||||||
|
|
||||||
|
- Yeni migration tablolarini ve kolonlarini dogrular.
|
||||||
|
- `tr/en` seed ve default `tr` ayarini dogrular.
|
||||||
|
- `user_preferences.language` alaninin `fr` gibi yeni locale degeri kabul
|
||||||
|
ettigini dogrular.
|
||||||
|
- Owner/client negatif authorization testlerini calistirir.
|
||||||
|
- Invalid locale, duplicate built-in locale, self fallback ve fallback cycle
|
||||||
|
durumlarini reddeder.
|
||||||
|
- Draft locale'in default yapilamadigini dogrular.
|
||||||
|
- Referansli ve built-in locale'in arsivlenemedigini dogrular.
|
||||||
|
- Backup alip restore eder; `instance_i18n_settings` ve `content_translations`
|
||||||
|
verisinin restore sonrasinda korundugunu dogrular.
|
||||||
|
|
||||||
|
## Faz-2'ye kalan sinir
|
||||||
|
|
||||||
|
Bu faz henuz UI metinlerini cevirmedi ve runtime translator eklemedi. Faz-2'de
|
||||||
|
built-in kataloglar, locale resolver, formatter ve client/server translator
|
||||||
|
katmani bu veri modelini kullanacak.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
title: Faz 2 Ceviri Runtime'i ve Built-in Kataloglar
|
||||||
|
description: Neta cok dillilik runtime, katalog, fallback, formatter, locale resolver ve smoke sonuclari.
|
||||||
|
phase: 2
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 2 Ceviri Runtime'i ve Built-in Kataloglar
|
||||||
|
|
||||||
|
Faz-2, Faz-1'de eklenen locale veri modelini calisan runtime katmanina bagladi.
|
||||||
|
Bu faz tum sayfa metinlerini henuz migrate etmez; sayfa sayfa UI migration Faz-4
|
||||||
|
kapsaminda yapilacak.
|
||||||
|
|
||||||
|
## Eklenen runtime parcalari
|
||||||
|
|
||||||
|
| Alan | Dosya |
|
||||||
|
| --- | --- |
|
||||||
|
| I18n tipleri ve namespace listesi | `lib/i18n/types.ts` |
|
||||||
|
| Built-in katalog registry ve parity helper | `lib/i18n/catalog.ts` |
|
||||||
|
| Interpolation, plural, tarih, sayi ve para formatter | `lib/i18n/format.ts` |
|
||||||
|
| Client-safe translator | `lib/i18n/translator.ts` |
|
||||||
|
| React client provider ve hook'lar | `components/i18n/i18n-provider.tsx` |
|
||||||
|
| TR built-in katalog | `locales/tr/*` |
|
||||||
|
| EN built-in katalog | `locales/en/*` |
|
||||||
|
| Server catalog merge ve catalogVersion cache | `server/i18n/catalog.ts` |
|
||||||
|
| Locale cookie ve direction helper | `server/i18n/locale.ts` |
|
||||||
|
| Session/cookie/instance locale resolver | `server/i18n/resolver.ts` |
|
||||||
|
| Server `createTranslator` helper | `server/i18n/translator.ts` |
|
||||||
|
| Public localization metadata | `server/i18n/runtime.ts` |
|
||||||
|
| Locale cookie lifecycle route | `app/api/i18n/locale/route.ts` |
|
||||||
|
|
||||||
|
## Davranis
|
||||||
|
|
||||||
|
- Built-in `tr` ve `en` kataloglari ayni namespace/key setine sahiptir.
|
||||||
|
- Ceviri anahtarlari `namespace.key` formatinda kullanilir.
|
||||||
|
- Interpolation `{name}` formatini destekler.
|
||||||
|
- Basit plural formatinda `Intl.PluralRules` kullanilir.
|
||||||
|
- DB override, built-in katalog ustune merge edilir.
|
||||||
|
- Fallback sirasi locale kaydindaki fallback chain'e gore calisir.
|
||||||
|
- Eksik anahtar production'da ham key'e duser; development'ta console warning
|
||||||
|
verir.
|
||||||
|
- `catalog_version` override mutation'inda artar ve cache key'ine dahil edilir.
|
||||||
|
- `<html lang>` ve `<html dir>` request locale resolver'dan gelir.
|
||||||
|
- `neta_locale` cookie'si `/api/i18n/locale` route'u uzerinden yazilir.
|
||||||
|
- `/api/v1/meta` response'u localization metadata'si dondurur.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Calistirilan komutlar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm i18n:phase2-smoke
|
||||||
|
pnpm build
|
||||||
|
pnpm phase9:smoke
|
||||||
|
pnpm typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
Sonuclar basarili. `i18n:phase2-smoke` su kapilari dogrular:
|
||||||
|
|
||||||
|
- TR/EN katalog parity.
|
||||||
|
- Ayni translation helper ile TR ve EN render.
|
||||||
|
- Interpolation ve plural format.
|
||||||
|
- Ortak tarih ve para formatter.
|
||||||
|
- RTL direction helper.
|
||||||
|
- Fransizca eksik anahtarda EN fallback.
|
||||||
|
- DB UI translation override'in deployment olmadan okunmasi.
|
||||||
|
- Override sonrasi `catalog_version` bump ile yeni metnin okunmasi.
|
||||||
|
- Owner disi actor'un UI translation yazamamasi.
|
||||||
|
|
||||||
|
`pnpm build` sirasinda mevcut Next edge runtime static generation uyarisi tekrar
|
||||||
|
goruldu; Faz-2 kaynakli yeni bir hata degil.
|
||||||
|
|
||||||
|
## Faz-4'e kalan sinir
|
||||||
|
|
||||||
|
Bu fazda mevcut sayfa metinleri henuz topluca katalog anahtarlarina tasinmadi.
|
||||||
|
Dashboard, musteri, proje, gorev, finans, gunluk, chat ve portal ekranlarindaki
|
||||||
|
hardcoded metinlerin migration'i Faz-4 ve Faz-6 kapsaminda yapilacak.
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
title: Faz 3 Ayarlar Dil ve Ceviri Yonetimi
|
||||||
|
description: Ayarlar ekraninda locale yonetimi, UI translation editor, import/export ve dogrulama sonuclari.
|
||||||
|
phase: 3
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 3 Ayarlar Dil ve Ceviri Yonetimi
|
||||||
|
|
||||||
|
Faz-3, Faz-1 ve Faz-2 i18n altyapisini owner tarafindan yonetilebilir hale
|
||||||
|
getirdi.
|
||||||
|
|
||||||
|
## Eklenenler
|
||||||
|
|
||||||
|
- Ayarlar sayfasina `Diller ve ceviriler` tab'i eklendi.
|
||||||
|
- Owner yeni locale ekleyebilir.
|
||||||
|
- Locale `draft`, `active`, `archived` lifecycle'i UI uzerinden yonetilebilir.
|
||||||
|
- Aktif locale instance default dili yapilabilir.
|
||||||
|
- Freelancer kendi arayuz dilini secebilir; tercih `user_preferences.language`
|
||||||
|
ve `neta_locale` cookie'sine yazilir.
|
||||||
|
- Locale kartlarinda genel tamamlanma yuzdesi ve eksik anahtar sayisi gorunur.
|
||||||
|
- Namespace ve metin aramali UI translation editor eklendi.
|
||||||
|
- Editor Turkce ve Ingilizce referans metnini ayni satirda gosterir.
|
||||||
|
- Tekil translation override kaydedilebilir ve sifirlanabilir.
|
||||||
|
- JSON export paketi uretilebilir.
|
||||||
|
- JSON import preview ve commit akisi eklendi.
|
||||||
|
- I18n mutation'lari `runtime_events` icine `i18n.settings` olarak audit edilir.
|
||||||
|
- Import/export round-trip ve client negatif yetki smoke testi eklendi.
|
||||||
|
|
||||||
|
## Degisen dosyalar
|
||||||
|
|
||||||
|
| Alan | Dosya |
|
||||||
|
| --- | --- |
|
||||||
|
| Settings UI | `app/(dashboard)/settings/page.tsx` |
|
||||||
|
| Settings server actions | `app/(dashboard)/settings/actions.ts` |
|
||||||
|
| I18n service export/import/completion | `server/i18n/service.ts` |
|
||||||
|
| UI translation repository helpers | `server/repositories/i18n.ts` |
|
||||||
|
| User language preference service | `server/settings/preferences.ts` |
|
||||||
|
| Faz-3 smoke | `scripts/i18n-phase3-smoke.*` |
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Calistirilan komutlar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm i18n:phase3-smoke
|
||||||
|
pnpm build
|
||||||
|
pnpm phase9:smoke
|
||||||
|
```
|
||||||
|
|
||||||
|
Sonuclar basarili. `pnpm build` sirasinda mevcut Next edge runtime static
|
||||||
|
generation uyarisi tekrar goruldu; Faz-3 kaynakli yeni bir hata degil.
|
||||||
|
|
||||||
|
## Faz-4'e kalan sinir
|
||||||
|
|
||||||
|
Bu faz ceviri yonetimini ekledi; uygulamadaki tum hardcoded metinleri henuz
|
||||||
|
katalog anahtarina tasimadi. Sayfa sayfa metin migration'i Faz-4'te yapilacak.
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
---
|
||||||
|
title: Faz 4 Kalan Hardcoded Metin Raporu
|
||||||
|
phase: 4
|
||||||
|
status: generated
|
||||||
|
last_updated: 2026-07-18T23:56:57.608Z
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 4 Kalan Hardcoded Metin Raporu
|
||||||
|
|
||||||
|
Bu rapor Faz-4 boundary script'i tarafindan uretilir. Formatter sabitleri release blocker kabul edilir; kalan Turkce stringler Faz-4 kapsaminda raporlanir ve sonraki UI migration dalgalarinda eritilir.
|
||||||
|
|
||||||
|
Toplam kalan Turkce literal satiri: 294
|
||||||
|
|
||||||
|
- `app/(dashboard)/analytics/analytics-client.tsx:56` <SelectValue placeholder="Tarih aralığı" />
|
||||||
|
- `app/(dashboard)/business/subscriptions/subscriptions-client.tsx:38` return "Aylık";
|
||||||
|
- `app/(dashboard)/business/subscriptions/subscriptions-client.tsx:40` return "Yıllık";
|
||||||
|
- `app/(dashboard)/business/subscriptions/subscriptions-client.tsx:42` return "Haftalık";
|
||||||
|
- `app/(dashboard)/calendar/actions.ts:17` title: requiredText(formData.get("title"), "Etkinlik başlığı zorunludur."),
|
||||||
|
- `app/(dashboard)/calendar/actions.ts:46` if (!value.startsAt) throw new Error("Etkinlik başlangıç zamanı zorunludur.");
|
||||||
|
- `app/(dashboard)/calendar/actions.ts:53` const id = requiredText(formData.get("id"), "Etkinlik kaydı bulunamadı.");
|
||||||
|
- `app/(dashboard)/calendar/actions.ts:55` if (!value.startsAt) throw new Error("Etkinlik başlangıç zamanı zorunludur.");
|
||||||
|
- `app/(dashboard)/calendar/actions.ts:64` requiredText(formData.get("id"), "Silinecek etkinlik bulunamadı."),
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:55` meeting: "Toplantı",
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:58` personal: "Kişisel",
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:134` {["Pzt", "Sal", "Çar", "Per", "Cum", "Cmt", "Paz"].map((day) => (
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:245` {event.projectName || event.clientName || event.taskTitle || event.description || "Bağlantı yok"}
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:293` toast.success(mode === "create" ? "Etkinlik eklendi." : "Etkinlik güncellendi.");
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:298` : "Etkinlik kaydedilirken beklenmeyen bir hata oluştu.",
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:310` {mode === "create" ? "Etkinlik ekle" : "Düzenle"}
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:317` <DialogTitle>{mode === "create" ? "Yeni etkinlik" : "Etkinliği düzenle"}</DialogTitle>
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:328` {isSubmitting ? "Kaydediliyor" : mode === "create" ? "Etkinliği ekle" : "Değişiklikleri kaydet"}
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:356` <Input name="title" defaultValue={event?.title || ""} required placeholder="Örn. Müşteri toplantısı" />
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:363` <SelectField name="type" label="Tür" defaultValue={event?.type || "focus"}>
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:370` <SelectField name="client_id" label="Müşteri" defaultValue={event?.client_id || "__none"}>
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:380` <SelectField name="task_id" label="Görev" defaultValue={event?.task_id || "__none"}>
|
||||||
|
- `app/(dashboard)/calendar/calendar-client.tsx:404` <SelectTrigger><SelectValue placeholder={`${label} seç`} /></SelectTrigger>
|
||||||
|
- `app/(dashboard)/chat/page.tsx:58` toast.error(error.message || "Yapay zeka ile iletişim kurulurken bir hata oluştu.");
|
||||||
|
- `app/(dashboard)/chat/page.tsx:74` toast.error(error instanceof Error ? error.message : "Sohbetler yüklenemedi.");
|
||||||
|
- `app/(dashboard)/chat/page.tsx:97` toast.error(error instanceof Error ? error.message : "Mesajlar yüklenemedi.");
|
||||||
|
- `app/(dashboard)/chat/page.tsx:143` toast.error(error instanceof Error ? error.message : "Sohbet oluşturulamadı.");
|
||||||
|
- `app/(dashboard)/chat/page.tsx:189` {session.title || "İsimsiz sohbet"}
|
||||||
|
- `app/(dashboard)/chat/page.tsx:196` aria-label={`${session.title || "İsimsiz sohbet"} sohbetini sil`}
|
||||||
|
- `app/(dashboard)/chat/page.tsx:302` placeholder="Mesaj gönder..."
|
||||||
|
- `app/(dashboard)/clients/[id]/actions.ts:19` title: requiredText(formData.get("title"), "Aktivite başlığı zorunludur."),
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:95` throw new Error(data.error || "Kullanıcı oluşturulamadı.");
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:99` toast.success("Güvenli portal daveti oluşturuldu.");
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:101` toast.error(error instanceof Error ? error.message : "Davet oluşturulamadı.");
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:116` if (!response.ok || data.error) throw new Error(data.error || "Portal dili güncellenemedi.");
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:117` toast.success("Portal dili güncellendi.");
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:120` toast.error(error instanceof Error ? error.message : "Portal dili güncellenemedi.");
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:165` <SelectValue placeholder="Dil seç" />
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:185` aria-label="Davet bağlantısını kopyala"
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:188` toast.success("Davet bağlantısı kopyalandı.");
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:310` <Input name="title" required placeholder="Aktivite özeti" />
|
||||||
|
- `app/(dashboard)/clients/[id]/client-detail-client.tsx:318` <Textarea name="content" rows={4} placeholder="Görüşme detayları..." />
|
||||||
|
- `app/(dashboard)/clients/actions.ts:25` name: requiredText(formData.get("name"), "Müşteri adı zorunludur."),
|
||||||
|
- `app/(dashboard)/clients/actions.ts:45` const id = requiredText(formData.get("id"), "Müşteri kaydı bulunamadı.");
|
||||||
|
- `app/(dashboard)/clients/actions.ts:53` const id = requiredText(formData.get("id"), "Arşivlenecek müşteri bulunamadı.");
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:76` { id: "contacted", label: "İletişime Geçildi", color: "border-blue-200 bg-blue-50 text-blue-700" },
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:77` { id: "proposal_sent", label: "Teklif İletildi", color: "border-amber-200 bg-amber-50 text-amber-700" },
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:78` { id: "won", label: "Kazanıldı (Won)", color: "border-emerald-200 bg-emerald-50 text-emerald-700" },
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:126` toast.success("Müşteri aşaması güncellendi.");
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:135` : "Müşteri aşaması güncellenemedi.",
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:175` label="Aktif Müşteri"
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:187` label="Kayıtlı Gelir"
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:189` description="Ödenmiş gelir işlemleri"
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:205` placeholder="Müşteri, firma, e-posta veya not ara"
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:433` toast.success(mode === "create" ? "Müşteri eklendi." : "Müşteri güncellendi.");
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:438` : "Müşteri kaydedilirken beklenmeyen bir hata oluştu.",
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:454` {mode === "create" ? "Müşteri ekle" : "Düzenle"}
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:463` {mode === "create" ? "Yeni müşteri" : "Müşteriyi düzenle"}
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:480` ? "Müşteriyi ekle"
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:481` : "Değişiklikleri kaydet"}
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:501` placeholder="Örn. Acme Corp"
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:520` <SelectValue placeholder="Aşama seç" />
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:533` <SelectValue placeholder="Durum seç" />
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:583` placeholder="İletişim notları, beklentiler, özel bilgiler..."
|
||||||
|
- `app/(dashboard)/clients/clients-client.tsx:610` {hasQuery ? "Aramana uygun müşteri yok" : "Henüz müşteri eklenmedi"}
|
||||||
|
- `app/(dashboard)/dashboard-client.tsx:250` {project.status === 'completed' ? 'Tamamlandı' : project.status === 'active' ? 'Aktif' : 'Beklemede'}
|
||||||
|
- `app/(dashboard)/finance/actions.ts:52` const id = requiredText(formData.get("id"), "Finans kaydı bulunamadı.");
|
||||||
|
- `app/(dashboard)/finance/actions.ts:67` requiredText(formData.get("id"), "Silinecek finans kaydı bulunamadı."),
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:68` planned: "Planlandı",
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:70` paid: "Ödendi",
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:71` cancelled: "İptal edildi",
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:84` { value: "TRY", label: "Türk lirası (TRY)" },
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:86` { value: "CAD", label: "Kanada doları (CAD)" },
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:87` { value: "AUD", label: "Avustralya doları (AUD)" },
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:93` { key: "afterTax", label: "Vergi Sonrası Net", tone: "green", icon: Wallet, featured: true },
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:94` { key: "net", label: "Brüt kazanç", tone: "primary", icon: Wallet, featured: true },
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:95` { key: "income", label: "Aylık gelir", tone: "green", icon: ArrowUpRight, featured: false },
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:96` { key: "expense", label: "Aylık gider", tone: "rose", icon: ArrowDownRight, featured: false },
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:177` aria-label="Önceki finans özet kartları"
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:187` aria-label="Sonraki finans özet kartları"
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:198` aria-label="Kaydırılabilir finans özeti"
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:240` placeholder="Kategori, müşteri, proje veya açıklama ara"
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:335` {transaction.category || "Kategori yok"} · {transaction.projectName || transaction.clientName || "Bağlantı yok"}
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:383` toast.success(mode === "create" ? "İşlem eklendi." : "İşlem güncellendi.");
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:388` : "Finans işlemi kaydedilirken beklenmeyen bir hata oluştu.",
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:400` {mode === "create" ? "İşlem ekle" : "Düzenle"}
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:407` <DialogTitle>{mode === "create" ? "Yeni finans işlemi" : "Finans işlemini düzenle"}</DialogTitle>
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:416` {isSubmitting ? "Kaydediliyor" : mode === "create" ? "İşlemi ekle" : "Değişiklikleri kaydet"}
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:476` <SelectField name="payment_status" label="Ödeme durumu" defaultValue={transaction?.payment_status || "planned"}>
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:492` <SelectValue placeholder="Para birimi seç" />
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:513` <Input name="category" defaultValue={transaction?.category || ""} placeholder="Örn. Yazılım, müşteri ödemesi, vergi" />
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:526` <SelectValue placeholder="Müşteri seç" />
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:542` <SelectValue placeholder="Proje seç" />
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:568` <SelectTrigger><SelectValue placeholder={`${label} seç`} /></SelectTrigger>
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:580` {hasQuery ? "Aramana uygun işlem yok" : "Henüz finans işlemi eklenmedi"}
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:584` ? "Arama metnini sadeleştirerek tekrar deneyebilirsin."
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:585` : "İlk gelir veya gider kaydını ekleyerek aylık finans özetini oluşturmaya başlayabilirsin."}
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:647` throw new Error(data.error || "Bilinmeyen bir hata oluştu.");
|
||||||
|
- `app/(dashboard)/finance/finance-client.tsx:652` `Hata: ${error instanceof Error ? error.message : "Bilinmeyen bir hata oluştu."}`,
|
||||||
|
- `app/(dashboard)/journal/actions.ts:15` if (!moodScore || !energyScore) throw new Error("Mood ve enerji skorları zorunludur.");
|
||||||
|
- `app/(dashboard)/journal/actions.ts:35` requiredText(formData.get("id"), "Günlük kaydı bulunamadı."),
|
||||||
|
- `app/(dashboard)/journal/actions.ts:45` requiredText(formData.get("id"), "Silinecek günlük kaydı bulunamadı."),
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:57` 1: "Çok düşük",
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:58` 2: "Düşük",
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:60` 4: "İyi",
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:61` 5: "Çok iyi",
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:114` label="Kayıtlı gün"
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:257` toast.success(mode === "create" ? "Günlük eklendi." : "Günlük güncellendi.");
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:262` : "Günlük kaydedilirken beklenmeyen bir hata oluştu.",
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:274` {mode === "create" ? "Günlük ekle" : "Düzenle"}
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:281` <DialogTitle>{mode === "create" ? "Yeni günlük kayıt" : "Günlük kaydı düzenle"}</DialogTitle>
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:293` {isSubmitting ? "Kaydediliyor" : mode === "create" ? "Kaydı ekle" : "Değişiklikleri kaydet"}
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:332` label="Çalışma memnuniyeti"
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:343` placeholder="Bugün nasıl geçti, enerjini etkileyen şeyler nelerdi?"
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:418` insights.push("Henüz okunabilir bir trend yok.");
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:420` insights.push(`Toplam ${logs.length} günlük kayıt var.`);
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:423` ? "Enerji ortalaması düşük. Dashboard raporlarında geciken işler ile birlikte okunmalı."
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:424` : "Enerji ortalaması dengeli görünüyor.",
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:428` ? "Mood seviyesi güçlü. Yüksek odak isteyen işler için iyi bir dönem olabilir."
|
||||||
|
- `app/(dashboard)/journal/journal-client.tsx:429` : "Mood trendi izlenmeli. Not alanı hangi günlerin zor geçtiğini anlamak için önemli.",
|
||||||
|
- `app/(dashboard)/layout.tsx:35` const displayName = profile.displayName || user.name || user.email.split("@")[0] || "Neta Kullanıcısı";
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:136` client_project: "Müşteri projesi",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:143` paused: "Duraklatıldı",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:144` completed: "Tamamlandı",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:145` cancelled: "İptal edildi",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:164` overview: "Genel bakış",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:165` problem: "Çözdüğü problem",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:166` goal: "Amaç",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:172` assets: "Görsel varlıklar",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:249` pendingChildren="Tamamlanıyor"
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:278` <InfoItem label="Tür" value={typeLabels[project.type]} icon={FolderKanban} />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:280` label="Müşteri"
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:281` value={project.clientName || "Bağımsız side project"}
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:290` label="Bütçe"
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:294` : "Bütçe yok"
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:303` <StatCard label="İlerleme" value={`${project.progress}%`} icon={Target} />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:304` <StatCard label="Görev" value={`${doneTaskCount}/${tasks.length}`} icon={ClipboardList} />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:339` title="Planlama alanları"
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:340` description="Problem, amaç, hedef kitle, kapsam ve proje notlarını burada tut."
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:351` description="Renk paleti, tipografi, görsel dil ve asset notlarını proje kaynağına bağla."
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:388` : "Revizyon durumu güncellenemedi.",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:517` {section.content || "İçerik eklenmedi."}
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:572` {mode === "create" ? "Planlama alanı ekle" : "Planlama alanını düzenle"}
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:584` <SelectValue placeholder="Kategori seç" />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:668` : "Görev durumu güncellenemedi.",
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:757` ? "Tamamlandı"
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:760` : "Yapılacak"}
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:784` {pendingTaskIds.has(task.id) ? "Tamamlanıyor" : "Tamamla"}
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:803` <EmptyPanel icon={ClipboardList} title="Bu projeye bağlı görev yok" />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:1059` <SelectValue placeholder="Durum seç" />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:1072` <SelectValue placeholder="Öncelik seç" />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:1135` {isSubmitting ? "Kaydediliyor" : "Görevi ekle"}
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:1181` <EmptyPanel icon={Wallet} title="Bu projeye bağlı finans kaydı yok" />
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:1288` if (status === "done") return "Tamamlandı";
|
||||||
|
- `app/(dashboard)/projects/[id]/project-detail-client.tsx:1290` return "Yapılacak";
|
||||||
|
- `app/(dashboard)/projects/actions.ts:32` name: localized.name ?? requiredText(formData.get("name"), "Proje adı zorunludur."),
|
||||||
|
- `app/(dashboard)/projects/actions.ts:86` const id = requiredText(formData.get("id"), "Proje kaydı bulunamadı.");
|
||||||
|
- `app/(dashboard)/projects/actions.ts:96` const id = requiredText(formData.get("id"), "Tamamlanacak proje bulunamadı.");
|
||||||
|
- `app/(dashboard)/projects/actions.ts:107` title: localized.title ?? requiredText(formData.get("title"), "Planlama başlığı zorunludur."),
|
||||||
|
- `app/(dashboard)/projects/actions.ts:129` const id = requiredText(formData.get("id"), "Planlama alanı bulunamadı.");
|
||||||
|
- `app/(dashboard)/projects/actions.ts:132` throw new Error("Planlama alanı bu projeye ait değil.");
|
||||||
|
- `app/(dashboard)/projects/actions.ts:147` const id = requiredText(formData.get("id"), "Silinecek planlama alanı bulunamadı.");
|
||||||
|
- `app/(dashboard)/projects/actions.ts:150` throw new Error("Planlama alanı bu projeye ait değil.");
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:77` client_project: "Müşteri projesi",
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:84` paused: "Duraklatıldı",
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:85` completed: "Tamamlandı",
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:86` cancelled: "İptal edildi",
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:161` placeholder="Proje, müşteri veya açıklama ara"
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:248` aria-label={`${project.name} detayına git`}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:276` {project.description || "Açıklama eklenmedi."}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:333` {project.clientName || "Bağımsız side project"}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:354` <div>{project.clientName || "Müşteri bağlantısı yok"}</div>
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:359` <div>{project.budget_amount ? formatCurrency(project.budget_amount) : "Bütçe yok"}</div>
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:437` toast.success(mode === "create" ? "Proje eklendi." : "Proje güncellendi.");
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:442` : "Proje kaydedilirken beklenmeyen bir hata oluştu.",
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:456` title={mode === "create" ? "Proje ekle" : "Düzenle"}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:457` aria-label={mode === "create" ? "Proje ekle" : "Düzenle"}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:460` {iconOnly ? null : mode === "create" ? "Proje ekle" : "Düzenle"}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:467` <DialogTitle>{mode === "create" ? "Yeni proje" : "Projeyi düzenle"}</DialogTitle>
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:490` : "Değişiklikleri kaydet"}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:539` alt={project?.cover_image_alt || project?.name || "Proje kapak görseli önizlemesi"}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:614` <SelectValue placeholder="Tür seç" />
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:630` <SelectValue placeholder="Müşteri seç" />
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:648` <SelectValue placeholder="Durum seç" />
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:736` {hasQuery ? "Aramana uygun proje yok" : "Henüz proje eklenmedi"}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:740` ? "Arama metnini sadeleştirerek tekrar deneyebilirsin."
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:741` : "İlk müşteri projen veya side project kaydınla operasyon akışını kurmaya başlayabilirsin."}
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:779` throw new Error(data.error || "Bilinmeyen bir hata oluştu.");
|
||||||
|
- `app/(dashboard)/projects/projects-client.tsx:785` (err instanceof Error ? err.message : "Bilinmeyen bir hata oluştu."),
|
||||||
|
- `app/(dashboard)/settings/actions.ts:113` return { error: error instanceof Error ? error.message : "Profil güncellenemedi." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:122` return { error: "Mevcut şifre zorunludur; yeni şifre en az 8 karakter olmalıdır." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:137` return { error: "Mevcut şifre doğrulanamadı veya şifre güncellenemedi." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:216` return { error: error instanceof Error ? error.message : "Dil durumu güncellenemedi." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:229` return { error: error instanceof Error ? error.message : "Varsayılan dil güncellenemedi." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:247` return { error: error instanceof Error ? error.message : "Çeviri kaydedilemedi." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:264` return { error: error instanceof Error ? error.message : "Çeviri sıfırlanamadı." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:275` return { error: error instanceof Error ? error.message : "Çeviri paketi dışa aktarılamadı." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:290` return { error: error instanceof Error ? error.message : "Import paketi okunamadı." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:303` return { error: error instanceof Error ? error.message : "Çeviri paketi içe aktarılamadı." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:321` return { error: "Workspace adı 1-120 karakter arasında olmalıdır." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:324` return { error: "Tarayıcı başlığı 1-80 karakter arasında olmalıdır." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:327` return { error: "Kısa uygulama adı 1-24 karakter arasında olmalıdır." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:330` return { error: "Ana renk #RRGGBB formatında olmalıdır." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:394` return { error: "Geçersiz marka görseli." };
|
||||||
|
- `app/(dashboard)/settings/actions.ts:410` return { error: error instanceof Error ? error.message : "Marka görseli kaldırılamadı." };
|
||||||
|
- `app/(dashboard)/settings/page.tsx:91` label: "Açık",
|
||||||
|
- `app/(dashboard)/settings/page.tsx:92` description: "Her zaman aydınlık renk paletini kullanır.",
|
||||||
|
- `app/(dashboard)/settings/page.tsx:98` description: "Her zaman koyu renk paletini kullanır.",
|
||||||
|
- `app/(dashboard)/settings/page.tsx:104` description: "Cihazınızın görünüm tercihini otomatik takip eder.",
|
||||||
|
- `app/(dashboard)/settings/page.tsx:162` nativeName: "Français",
|
||||||
|
- `app/(dashboard)/settings/page.tsx:176` { name: "Diller ve çeviriler", icon: Languages },
|
||||||
|
- `app/(dashboard)/settings/page.tsx:237` toast.success("Profil güncellendi!");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:248` toast.success("Şifre güncellendi!");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:261` toast.success("Yapay Zeka ayarları kaydedildi!");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:281` toast.success("Görünüm tercihi kaydedildi.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:308` toast.success("Genel görünüm ve marka ayarları güncellendi.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:324` toast.success("Marka görseli kaldırıldı.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:349` toast.success("Arayüz dili kaydedildi.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:370` toast.success("Dil durumu güncellendi.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:380` toast.success("Varsayılan dil güncellendi.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:396` toast.success("Çeviri kaydedildi.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:410` toast.success("Override sıfırlandı.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:418` toast.error(response.error ?? "Export oluşturulamadı.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:422` toast.success("Çeviri paketi aşağıdaki alana yazıldı.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:429` toast.error(response.error ?? "Import paketi okunamadı.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:432` setImportPreview(`${response.preview.localeCount} dil, ${response.preview.translationCount} çeviri, default: ${response.preview.defaultLocale}`);
|
||||||
|
- `app/(dashboard)/settings/page.tsx:441` toast.success("Çeviri paketi içe aktarıldı.");
|
||||||
|
- `app/(dashboard)/settings/page.tsx:594` description="Kare PNG önerilir; en fazla 5 MB."
|
||||||
|
- `app/(dashboard)/settings/page.tsx:620` aria-label="Ana renk seçici"
|
||||||
|
- `app/(dashboard)/settings/page.tsx:678` aria-label="Tema görünümü"
|
||||||
|
- `app/(dashboard)/settings/page.tsx:726` ? "Görünüm tercihi kaydediliyor…"
|
||||||
|
- `app/(dashboard)/settings/page.tsx:727` : "Değişiklik tüm sayfalara anında uygulanır."}
|
||||||
|
- `app/(dashboard)/settings/page.tsx:734` {activeTab === "Diller ve çeviriler" && (
|
||||||
|
- `app/(dashboard)/settings/page.tsx:795` {defaultLocale === locale.code ? "Varsayılan" : "Default yap"}
|
||||||
|
- `app/(dashboard)/settings/page.tsx:815` <Input value={newLocale.nativeName} onChange={(event) => setNewLocale((current) => ({ ...current, nativeName: event.target.value }))} placeholder="Français" />
|
||||||
|
- `app/(dashboard)/settings/page.tsx:1065` placeholder={hasApiKey ? "Kayıtlı anahtarı korumak için boş bırakın" : "sk-..."}
|
||||||
|
- `app/(dashboard)/settings/page.tsx:1171` alt={`${title} önizlemesi`}
|
||||||
|
- `app/(dashboard)/tasks/actions.ts:28` title: localized.title ?? requiredText(formData.get("title"), "Görev başlığı zorunludur."),
|
||||||
|
- `app/(dashboard)/tasks/actions.ts:72` const id = requiredText(formData.get("id"), "Görev kaydı bulunamadı.");
|
||||||
|
- `app/(dashboard)/tasks/actions.ts:81` const id = requiredText(formData.get("id"), "Tamamlanacak görev bulunamadı.");
|
||||||
|
- `app/(dashboard)/tasks/actions.ts:95` const id = requiredText(formData.get("id"), "Silinecek görev bulunamadı.");
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:64` todo: "Yapılacak",
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:66` done: "Tamamlandı",
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:70` low: "Düşük",
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:72` high: "Yüksek",
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:129` : "Görev durumu güncellenemedi.",
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:159` error instanceof Error ? error.message : "Görev silinemedi.",
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:214` <StatCard label="Toplam görev" value={localTasks.length.toString()} />
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:233` placeholder="Görev, proje veya müşteri ara"
|
||||||
|
- `app/(dashboard)/tasks/tasks-client.tsx:379` <div>{task.clientName || "Müşteri yok"}</div>
|
||||||
|
|
||||||
|
Ilk 250 satir listelendi; kalan: 44
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
title: Faz 4 Freelancer UI Migration
|
||||||
|
description: Freelancer uygulamasinda ortak shell, sidebar, dashboard ve locale-aware formatter migration sonuclari.
|
||||||
|
phase: 4
|
||||||
|
status: completed
|
||||||
|
last_updated: 2026-07-19
|
||||||
|
---
|
||||||
|
|
||||||
|
# Faz 4 Freelancer UI Migration
|
||||||
|
|
||||||
|
Faz-4, owner/freelancer uygulamasinda i18n runtime'in gercek UI yuzeylerine
|
||||||
|
baglanmasini baslatti ve teknik formatter sabitlerini kaldirdi.
|
||||||
|
|
||||||
|
## Tamamlananlar
|
||||||
|
|
||||||
|
- Dashboard layout request locale'e baglandi.
|
||||||
|
- Dashboard subtree `I18nProvider` ile sarildi.
|
||||||
|
- Sidebar grup ve linkleri katalog anahtarlarindan uretiliyor.
|
||||||
|
- App shell skip link, mobil menu, hesap dropdown'i, cikis ve progress metinleri
|
||||||
|
locale-aware hale geldi.
|
||||||
|
- Dashboard ana sayfasi baslik, KPI label, filtre, bolum basliklari ve empty
|
||||||
|
state metinlerinde katalog kullanmaya basladi.
|
||||||
|
- Müşteriler, projeler, görevler, finans, takvim, günlük ve sohbet ana
|
||||||
|
header'lari katalogdan okunuyor.
|
||||||
|
- Freelancer client formatter'larinda sabit `tr-TR` kullanimi kaldirildi.
|
||||||
|
- Freelancer `date-fns/locale/tr` importlari merkezi locale mapping'e tasindi.
|
||||||
|
- `/projects` server-side sort locale-aware hale geldi.
|
||||||
|
- Faz-4 boundary script'i eklendi.
|
||||||
|
- Kalan hardcoded metin envanteri otomatik raporlanir hale geldi.
|
||||||
|
|
||||||
|
## Kalan UI metin raporu
|
||||||
|
|
||||||
|
Kalan derin form label'lari, dialog metinleri ve toast metinleri su raporda
|
||||||
|
listelendi:
|
||||||
|
|
||||||
|
- [Faz 4 hardcoded text report](i18n-phase-4-hardcoded-text-report.md)
|
||||||
|
|
||||||
|
Bu rapor Faz-4 boundary script'i tarafindan uretilir. Formatter sabitleri fail
|
||||||
|
eder; kalan kullanici metinleri sonraki UI migration dalgalarinda eritilecek
|
||||||
|
istisna envanteri olarak tutulur.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Calistirilan komutlar:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm i18n:phase4-boundary
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm phase9:smoke
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
Sonuclar basarili. `pnpm build` sirasinda mevcut Next edge runtime static
|
||||||
|
generation uyarisi tekrar goruldu; Faz-4 kaynakli yeni bir hata degil.
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user