Merge PR #2295: i18n: Indonesian (id) interface language

Conflicts:
- crates/ui-egui/src/i18n.rs, crates/ui-egui/src/menus.rs: language table and commands keep sv, fi and id
- crates/ui-egui/src/panels/keyword_list.rs: code list gains id; Locale::ALL.len() is 17
- docs/localization.md: language list and note links keep every language
- docs/localization-parity.md: header keeps the newer date and both changes; links and revision rows are the union
This commit is contained in:
aiongg
2026-10-22 02:37:53 +00:00
12 changed files with 2753 additions and 14 deletions
+299
View File
@@ -0,0 +1,299 @@
{
" · {skipped} skipped (file exists)": " · {skipped} dilewati (file sudah ada)",
" · {} duplicate{} skipped": " · {} duplikat dilewati{:.0}",
" · {} failed: {}": " · {} gagal: {}",
" · {} moved": " · {} dipindahkan",
" · {} not readable": " · {} tidak dapat dibaca",
" · {} restored from Recently Deleted": " · {} dipulihkan dari Baru dihapus",
", {failed} file{} not readable": ", {failed} file tidak dapat dibaca{:.0}",
"Add {local_n} to My Photos": "Tambahkan {local_n} ke Foto saya",
"Add {} Photo{}": "Tambahkan {} foto{:.0}",
"Add {} Photo{}, restore {}": "Tambahkan {} foto{:.0}, pulihkan {}",
"Add “{}” to Favorites": "Tambahkan “{}” ke Favorit",
"Added {n} photo{} to My Photos": "{n} foto{:.0} ditambahkan ke Foto saya",
"Added {n} photo{} to “{}”": "{n} foto ditambahkan ke “{1}”{0:.0}",
"All {n} selected photos move by the same amount.": "Semua {n} foto yang dipilih digeser dengan selisih waktu yang sama.",
"Already in “{}”": "Sudah ada di “{}”",
"Auto Sync: edits apply to {n} photos": "Sinkronisasi otomatis: edit diterapkan ke {n} foto",
"Automatic ({} MB)": "Otomatis ({} MB)",
"Click {}:1": "Klik {}:1",
"Color space: Rec. 2020 {curve} (HDR AVIF)": "Ruang warna: Rec. 2020 {curve} (AVIF HDR)",
"Create {n} Virtual Copies": "Buat {n} salinan virtual",
"Decoder: {reason}": "Dekoder: {reason}",
"Delete \"{label}\"": "Hapus \"{label}\"",
"Delete \"{label}\" (and the mask)": "Hapus \"{label}\" (beserta maskernya)",
"Delete {n} Photos": "Hapus {n} foto",
"Delete “{current}”": "Hapus “{current}”",
"Duplicate \"{label}\"": "Duplikat \"{label}\"",
"Example: {example}": "Contoh: {example}",
"Exported {n} photo{}": "{n} foto{:.0} diekspor",
"Exported {ok} of {total} photo{}": "{ok} dari {total} foto diekspor{:.0}",
"Exported {} point curve preset{}": "{} preset kurva titik{:.0} diekspor",
"Exported {} preset{}": "{} preset{:.0} diekspor",
"Exporting {} of {total}": "Mengekspor {} dari {total}",
"Filter bar — {active} active filter{}": "Bilah filter — {active} filter aktif{:.0}",
"Healed {n} dust spot{}": "{n} bintik debu diperbaiki{:.0}",
"Imported {n} point curve preset{}": "{n} preset kurva titik diimpor{:.0}",
"Imported {n} preset{}": "{n} preset{:.0} diimpor",
"Imported {n} preset{}, {f} file{} not readable": "{n} preset diimpor{0:.0}, {f} file tidak dapat dibaca{1:.0}",
"Imported {} photo{}": "{} foto{:.0} diimpor",
"LightCraft could not open its window: {e}": "LightCraft tidak dapat membuka jendela: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft tidak dapat membuka jendela: {e}\n\nDetailnya ada di file log: {path}",
"Matches: {}": "Cocok: {}",
"Merging… {stage} {:.0}%": "Menggabungkan… {stage} {:.0}%",
"Move {n} photo{} to “{}”": "Pindahkan {n} foto{0:.0} ke “{1}”",
"Move {n} photo{} to “{}”?": "Pindahkan {n} foto{0:.0} ke “{1}”?",
"Move {what} to Recently Deleted?": "Pindahkan {what} ke Baru dihapus?",
"Moved {n} photo{} to “{}”. Edit → Undo moves them back.": "{n} foto dipindahkan ke “{1}”{0:.0}. Pilih Edit → Urungkan untuk mengembalikannya.",
"Named ({named_n})": "Bernama ({named_n})",
"Paste into {n} photo{} — only settings that were copied are pasted": "Tempel ke {n} foto{:.0} — hanya menempelkan pengaturan yang disalin",
"Preset: {name}": "Preset: {name}",
"Preview only — editing the camera's embedded JPEG ({})": "Hanya pratinjau — yang diedit adalah JPEG bawaan kamera ({})",
"Preview only — {}": "Hanya pratinjau — {}",
"Profile: {}": "Profil: {}",
"Proof Copy ({})": "Salinan soft proof ({})",
"Proof Preview · {}": "Pratinjau soft proof · {}",
"Quick Develop · {n} photos": "Olah cepat · {n} foto",
"Rated {}": "Diberi rating {}",
"Reading Lightroom catalog… {done} of {total}": "Membaca katalog Lightroom… {done} dari {total}",
"Reading photos… {done} of {total}": "Membaca foto… {done} dari {total}",
"Redo {}": "Ulangi {}",
"Remove “{}” from Favorites": "Keluarkan “{}” dari Favorit",
"Removed from {}": "Dikeluarkan dari {}",
"Rename {n} Photos…": "Ganti nama {n} foto…",
"Rename “{name}”": "Ganti nama “{name}”",
"Rendering on the CPU: {why}": "Merender dengan CPU: {why}",
"Reset {} Channel": "Atur ulang kanal {}",
"Restore {}": "Pulihkan {}",
"Restore {} Photo{}": "Pulihkan {} foto{:.0}",
"Restored {} photo{} from Recently Deleted": "{} foto{:.0} dipulihkan dari Baru dihapus",
"Saved export preset “{}”": "Preset ekspor “{}” disimpan",
"Show {hidden} hidden location{}": "Tampilkan {hidden} lokasi tersembunyi{:.0}",
"Showing {n} of {}": "Menampilkan {n} dari {}",
"Smart album: {}": "Album pintar: {}",
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "File sidecar XMP untuk {n} foto tidak dapat ditulis (apakah foldernya sedang offline?). LightCraft akan terus mencoba.",
"Undo {}": "Urungkan {}",
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "Versi {} — pustaka foto dan pengolah raw yang dibuat secara clean-room dengan Rust murni.",
"Version: {}": "Versi: {}",
"none (CPU rendering): {why}": "tidak ada (rendering CPU): {why}",
"the {n} selected photos": "{n} foto yang dipilih",
"the {} photos in view": "{} foto yang ditampilkan",
"these {count} photos": "{count} foto ini",
"{count} smart preview{} · {:.1} MB": "{count} pratinjau pintar{0:.0} · {1:.1} MB",
"{f} folders (and their subfolders)": "{f} folder (beserta subfoldernya)",
"{label} Grading": "Grading {label}",
"{label} {d:+} on every selected photo": "{label} {d:+} pada setiap foto yang dipilih",
"{label}, {n} photos": "{label}, {n} foto",
"{matching} of {t} photos": "{matching} dari {t} foto",
"{matching} photos": "{matching} foto",
"{mode} spot {} of {}": "{mode}: bintik {} dari {}",
"{msg} ({failed} failed)": "{msg} ({failed} gagal)",
"{name} Label": "Label {name}",
"{name} Off": "{name} nonaktif",
"{name} On": "{name} aktif",
"{name} — double-click to reset it": "{name} — klik dua kali untuk mengatur ulang",
"{n} change{} saved in memory but not written to disk: {e}\nLightCraft retries automatically; quitting now would lose {}.": "{n} perubahan{0:.0} tersimpan di memori, tetapi belum ditulis ke disk: {e}\nLightCraft akan otomatis mencoba lagi; jika Anda keluar sekarang, perubahan tersebut akan hilang.{1:.0}",
"{n} found · {sel} selected": "{n} ditemukan · {sel} dipilih",
"{n} photos": "{n} foto",
"{n} photos in {} are hidden by the filters above": "{n} foto di {} disembunyikan oleh filter di atas",
"{n} photo{}": "{n} foto{:.0}",
"{n} photo{} match · updates automatically as photos change": "{n} foto cocok{:.0} · diperbarui otomatis saat foto berubah",
"{n} photo{} now · updates automatically as photos change": "Saat ini {n} foto{:.0} · diperbarui otomatis saat foto berubah",
"{n} selected photos · {pages} pages": "{n} foto dipilih · {pages} halaman",
"{sel_n} selected · {counted}": "{sel_n} dipilih · {counted}",
"{stage}… {:.0}%": "{stage}… {:.0}%",
"{what} merge added": "Hasil penggabungan {what} ditambahkan",
"{what} merge failed: {e}": "Penggabungan {what} gagal: {e}",
"{} and higher": "{} ke atas",
"{} files": "{} file",
"{} files and {f} folder{}": "{} file dan {f} folder{:.0}",
"{} of 4 guides — drag along lines that should be vertical or horizontal.": "{} dari 4 garis panduan — seret di sepanjang garis yang seharusnya vertikal atau horizontal.",
"{} photos": "{} foto",
"{} photo{} not readable": "{} foto tidak dapat dibaca{:.0}",
"{} spot(s) on this photo": "{} bintik di foto ini",
"· {dups} already in the library": "· {dups} sudah ada di pustaka",
"· {n} photo{}": "· {n} foto{:.0}",
"· {trashed} in Recently Deleted": "· {trashed} di “Baru dihapus”",
"… and {more} more": "… dan {more} lainnya",
"Import cancelled · {} photo{} added": "Impor dibatalkan · {} foto{:.0} ditambahkan",
"Reading photos… {} of {}": "Membaca foto… {} dari {}",
"Adding photos… {} of {}": "Menambahkan foto… {} dari {}",
" ({} in Recently Deleted)": " ({} di Baru dihapus)",
"Already in the library: {} photo{} selected in All Photos": "Sudah ada di pustaka: {} foto{:.0} dipilih di Semua foto",
"Already in the library, in Recently Deleted: {} photo{} selected · right-click ▸ Restore, or Delete Permanently to import afresh": "Sudah ada di pustaka, di Baru dihapus: {} foto{:.0} dipilih · klik kanan → Pulihkan, atau Hapus permanen untuk mengimpor ulang",
"Auto ({})": "Otomatis ({})",
"Build 1:1 Previews ({scope})": "Buat pratinjau 1:1 ({scope})",
"Build Smart Previews ({scope})": "Buat pratinjau pintar ({scope})",
"Build Standard-Sized Previews ({scope})": "Buat pratinjau ukuran standar ({scope})",
"Discard Smart Previews ({scope})": "Buang pratinjau pintar ({scope})",
"Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Katalog Lightroom diperiksa: {photos} foto, {collections} koleksi, {missing} hilang",
"Lightroom import complete: {imported} photos added": "Impor Lightroom selesai: {imported} foto ditambahkan",
"Lightroom import complete: {imported} photos added, {failed} failed": "Impor Lightroom selesai: {imported} foto ditambahkan, {failed} gagal",
"Lightroom import complete: {imported} photos added; index warning: {warning}": "Impor Lightroom selesai: {imported} foto ditambahkan; peringatan indeks: {warning}",
"Lightroom import failed: {e}{warning}": "Impor Lightroom gagal: {e}{warning}",
"Lightroom inspection failed: {e}{warning}": "Pemeriksaan Lightroom gagal: {e}{warning}",
"Label Set: {name}": "Set label: {name}",
"Quick Develop: {name}": "Olah cepat: {name}",
"Reset {name}": "Atur ulang {name}",
"Updated “{name}”": "“{name}” diperbarui",
"{month}": "{month:02}",
"{month} {year}": "{month:02}/{year}",
"{verb} {n} Photo{}": "{verb} {n} foto{:.0}",
"{weekday}, {day}": "{weekday}, {day}",
"{weekday}, {day} {month} {year}": "{weekday}, {day:02}/{month:02}/{year}",
"{year}": "{year}",
"Remove “{name}” and its {count} photo{} from the library?": "Keluarkan “{name}” beserta {count} foto{:.0} di dalamnya dari pustaka?",
" ({unsure} with several look-alike files: use Locate)": " ({unsure} memiliki beberapa file serupa: gunakan “Temukan file hilang”)",
" · {:.0}% clipped": " · {:.0}% terpotong",
" · {failed} couldn't be rendered": " · {failed} gagal dirender",
" · {} original{} left at the source": " · {} file asli{:.0} tetap di sumber",
" — not carried over: {}{}": " — tidak ikut terbawa: {}{}",
"+{} more": "+{} lainnya",
"; {kept} already had a location": "; {kept} sudah memiliki lokasi",
"; {outside} outside its time range": "; {outside} di luar rentang waktunya",
"@{}: {} PRs, {} commits, +{} / −{} lines (Δ {}), +{} / −{} binary assets, {} – {}": "@{}: {} PR, {} commit, +{} / −{} baris (Δ {}), +{} / −{} aset biner, {} – {}",
"Added {what} to {name}": "{what} ditambahkan ke {name}",
"Auto Import: added {} photo{}": "Impor otomatis: {} foto{:.0} ditambahkan",
"Building previews for {} photos…": "Membuat pratinjau untuk {} foto…",
"Couldn't open the editor: {e}": "Tidak dapat membuka editor: {e}",
"Creates {} stacks from {} of {scope}": "Membuat {} tumpukan dari {} di antara {scope}",
"Done ({what}): {done} photo{plural}": "Selesai ({what}): {done} foto{plural:.0}",
"Focus {:.0}": "Fokus {:.0}",
"Folder “{}” (and its subfolders)": "Folder “{}” (beserta subfoldernya)",
"Found {n} missing photo{}; {left} still missing{unsure}": "Ditemukan {n} foto yang hilang{:.0}; {left} masih hilang{unsure}",
"Import failed: {e}": "Impor gagal: {e}",
"Import warnings ({n}):\n{warnings}": "Peringatan impor ({n}):\n{warnings}",
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "Profil XMP \"{profile}\" tidak diterapkan untuk {path}; warna mungkin berbeda dari Lightroom.",
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Geometri Lightroom untuk {path} tidak dipertahankan; {mode} akan dihitung ulang.",
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Geometri Lightroom untuk {path} tidak dipertahankan; transformasi tersimpan diabaikan karena Tegakkan nonaktif.",
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "Geometri Lightroom untuk {path} tidak dipertahankan; transformasi Tegakkan terpandu yang tersimpan tidak didukung.",
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Geometri Lightroom untuk {path} tidak dipertahankan; transformasi Tegakkan yang tersimpan tidak didukung.",
"XMP adjustments were not imported for {path}: {fields}.": "Penyesuaian XMP untuk {path} tidak diimpor: {fields}.",
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "{profiles} profil diimpor{:.0} (Penjelajah profil → grup masing-masing)",
"Insert at the cursor (also written {})": "Sisipkan di posisi kursor (dapat juga ditulis {})",
"MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Font: {} (OFL). Ikon: orisinal.",
"Painting “{k}”: click photos to add or remove it · Esc stops": "Menerapkan “{k}”: klik foto untuk menambahkan atau menghapusnya · Esc untuk berhenti",
"Previews ready for {done} photo{plural}": "Pratinjau untuk {done} foto{plural:.0} sudah siap",
"Removed {what} from {name}": "{what} dihapus dari {name}",
"Renames “{from}” on {n} photo{} (keywords below it too). Use | for levels, e.g. Travel|Italy. An existing name merges the two.": "Mengganti nama “{from}” di {n} foto{:.0} (termasuk kata kunci di bawahnya). Gunakan | untuk tingkatan, mis. Liburan|Italia. Jika nama sudah ada, keduanya akan digabungkan.",
"Replace {} with:": "Ganti {} dengan:",
"Scores {scope} for focus and exposure and finds similar shots taken within seconds of each other (bursts).": "Menilai fokus dan eksposur {scope}, lalu mencari bidikan serupa yang diambil berselang beberapa detik (bidikan beruntun).",
"Tagged {n} photo{} from the tracklog": "{n} foto diberi lokasi dari log jejak GPS{:.0}",
"The SAM 3 download failed: {e}": "Gagal mengunduh SAM 3: {e}",
"Unknown tag{s} {} stay{} as typed (see Tags)": "Tag tak dikenal{s:.0} {0} tetap seperti yang diketik{1:.0} (lihat Tag)",
"Virtual copy “{name}” of {of}": "Salinan virtual “{name}” dari {of}",
"Working on {what} for {} photos…": "Memproses {what} untuk {} foto…",
"{label} {index} of {n}": "{label} {index} dari {n}",
"{name} opened for editing; it is stacked with the original": "{name} dibuka untuk diedit dan ditumpuk dengan file aslinya",
"{n} change{} couldn't be written to disk: {e}\nIf you quit now, {} lost.": "{n} perubahan{0:.0} tidak dapat ditulis ke disk: {e}\nJika Anda keluar sekarang, perubahan tersebut akan hilang{1:.0}.",
"{n} change{} saved in memory but not written to disk: {e} — LightCraft will retry": "{n} perubahan{:.0} tersimpan di memori, tetapi belum ditulis ke disk: {e} — LightCraft akan mencoba lagi",
"{pid} (missing)": "{pid} (hilang)",
"{{date:…}} directives: {}": "Direktif {{date:…}}: {}",
"{} contributors · {} commits": "{} kontributor · {} commit",
"{} failed": "{} gagal",
"{} item{}": "{} item{:.0}",
"{} photos · exposures {} EV": "{} foto · eksposur {} EV",
"{} photos · {} · {:.0}° × {:.0}°": "{} foto · {} · {:.0}° × {:.0}°",
"{} sample{}": "{} sampel{:.0}",
"{} sec": "{} dtk",
"{} — show their photos": "{} — tampilkan foto orang ini",
"LightCraft can't decode this raw variant yet ({}). You're editing the camera's embedded JPEG preview, which already includes the camera's picture style (e.g. Monochrome) and white balance.": "LightCraft belum dapat mendekode varian raw ini ({}). Anda sedang mengedit pratinjau JPEG bawaan kamera, yang sudah menyertakan gaya gambar kamera (mis. Monokrom) dan keseimbangan putihnya.",
"Filtered: {label}": "Difilter: {label}",
"{n} similar photo{} · View ▸ Clear Filters to see all": "{n} foto serupa{:.0} · Tampilan → Hapus filter untuk melihat semua",
"Moved; {} photo(s) relinked": "Dipindahkan; {} foto ditautkan ulang",
"{} label (click more labels to show any of them)": "Label {} (klik label lain untuk ikut menampilkan foto berlabel tersebut)",
", {} star{}": ", {} bintang{:.0}",
", {} label": ", label {}",
"Downloads from {host}. Once it has arrived and checked out, LightCraft installs it, {after}.": "Diunduh dari {host}. Setelah selesai diunduh dan lolos pemeriksaan, LightCraft akan menginstalnya, {after}.",
"{:.1} MB": "{:.1} MB",
"Running on the processor: {}.": "Berjalan di prosesor: {}.",
"{} · {}. Kept in the library's “denoise” folder; the oldest ones that no photo uses go first when the limit is reached.": "{} · {}. Disimpan di folder “denoise” pustaka; saat batas tercapai, file terlama yang tidak dipakai foto mana pun dihapus lebih dulu.",
"Denoising {name}: {done} of {total} tiles{}": "Mengurangi noise {name}: {done} dari {total} petak{}",
"{size} GB": "{size} GB",
"{} of {} from {host}": "{} / {} dari {host}",
"{} KB": "{} KB",
"{} MB": "{} MB",
"Making the denoised picture… {}%": "Membuat gambar hasil pengurangan noise… {}%",
"{name} · {terms}": "{name} · {terms}",
"{n} {noun}": "{n} {noun}",
"Trained on: {provenance}": "Dilatih dengan: {provenance}",
" · {queued} waiting": " · {queued} menunggu",
"Shows the model's terms, then downloads {} from {host}, installs it and starts using it. Nothing else is sent.": "Menampilkan ketentuan model, lalu mengunduh {} dari {host}, menginstalnya, dan mulai menggunakannya. Tidak ada data lain yang dikirim.",
"In line for AI Denoise ({ahead} ahead)…": "Dalam antrean pengurangan noise AI ({ahead} di depan)…",
"Running on {}.": "Berjalan di {}.",
"{file} · {size}": "{file} · {size}",
"• {assumption}": "• {assumption}",
"Put inside “{parent}”": "Masukkan ke “{parent}”",
"Delete the keyword “{name}”?": "Hapus kata kunci “{name}”?",
"It is taken off {count} photo{}, with the keywords below it. Undo brings it back.": "Kata kunci ini akan dilepas dari {count} foto{:.0}, beserta kata kunci di bawahnya. Urungkan untuk mengembalikannya.",
"Create Keyword Tag Inside “{name}”…": "Buat tag kata kunci di dalam “{name}”…",
"“{parent}” has a “{name}” already. Merge the two?": "“{parent}” sudah berisi “{name}”. Gabungkan keduanya?",
"There is a “{name}” at the top level already. Merge the two?": "Sudah ada “{name}” di tingkat teratas. Gabungkan keduanya?",
"Added “{keyword}” to {n} photo{}": "“{keyword}” ditambahkan ke {n} foto{:.0}",
"On {have} of {of} selected photos": "Pada {have} dari {of} foto yang dipilih",
"Remove “{name}”": "Hapus “{name}”",
"There is a keyword set “{name}” already": "Set kata kunci “{name}” sudah ada",
"Added {n} keyword{}": "{n} kata kunci ditambahkan{:.0}",
"; {n} gained synonyms": "; {n} mendapat sinonim baru",
"Exported {n} keyword{}": "{n} kata kunci diekspor{:.0}",
" — Capture One won't import a list with ; , < or > in a keyword: {names}": " — Capture One tidak akan mengimpor daftar yang kata kuncinya mengandung ; , < atau >: {names}",
" — left out, as a keyword list can't hold them: {names}": " — tidak disertakan karena tidak dapat dimasukkan ke daftar kata kunci: {names}",
"{done} of {total}": "{done} dari {total}",
"{done} of {total} MB": "{done} dari {total} MB",
"{pct} %": "{pct} %",
"+{n} more": "+{n} lainnya",
"Create the keyword “{name}”": "Buat kata kunci “{name}”",
"new, inside {parent}": "baru, di dalam {parent}",
"Keywords of {n} selected photos": "Kata kunci {n} foto yang dipilih",
"Removed “{name}” from {n} photos. Edit ▸ Undo puts it back.": "“{name}” dihapus dari {n} foto. Edit → Urungkan akan mengembalikannya.",
"1 task is still running: {names}. Quitting stops it.": "1 tugas masih berjalan: {names}. Jika keluar sekarang, tugas ini akan dihentikan.",
"{n} tasks are still running: {names}. Quitting stops them.": "{n} tugas masih berjalan: {names}. Jika keluar sekarang, semua tugas ini akan dihentikan.",
"{what} merge cancelled": "Penggabungan {what} dibatalkan",
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "{n} foto Lokal{:.0} dilewati: album hanya dapat berisi foto pustaka (tambahkan ke Foto saya terlebih dahulu)",
"New Folder in “{name}”": "Folder baru di “{name}”",
"Moved {} photo(s)": "{} foto dipindahkan",
"not added to Apple Photos: {e}": "tidak ditambahkan ke Apple Photos: {e}",
"{n} added to Apple Photos album “{album}”": "{n} ditambahkan ke album Apple Photos “{album}”",
"{n} added to Apple Photos": "{n} ditambahkan ke Apple Photos",
"{n} photos opened for editing; each is stacked with its original": "{n} foto dibuka untuk diedit; masing-masing ditumpuk dengan file aslinya",
" in {g} group{}": " dalam {g} grup{:.0}",
" · kept, not applied: {}": " · dipertahankan, tidak diterapkan: {}",
" — kept but not applied: {}": " — disimpan, tetapi tidak diterapkan: {}",
"Apply to “{mask}” on Selected Photos": "Terapkan ke “{mask}” pada foto yang dipilih",
"Delete the effect preset “{name}”?": "Hapus preset efek “{name}”?",
"Delete these {n} effect presets?": "Hapus {n} preset efek ini?",
"Delete {n} effect preset{}? This can't be undone.": "Hapus {n} preset efek{:.0}? Tindakan ini tidak dapat diurungkan.",
"Effect: {name}": "Efek: {name}",
"Exported {} effect preset{}": "{} preset efek diekspor{:.0}",
"Imported {e} mask effect{} (Masking ▸ Effect)": "{e} efek masker{:.0} diimpor (Masker → Efek)",
"Update “{name}” with Current Settings": "Perbarui “{name}” dengan pengaturan saat ini",
"{n} preset{}": "{n} preset{:.0}",
"{name} opened in the other editor": "{name} dibuka di editor lain",
"{n} originals opened in the other editor": "{n} file asli dibuka di editor lain",
"· {in_source} identical copies": "· {in_source} salinan identik",
"{count} more warnings.": "{count} peringatan lainnya.",
"{photos} catalog records processed; {collections} collections.": "{photos} entri katalog diproses; {collections} koleksi.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} file asli hilang. Data katalognya masih tersedia; gunakan “Cari foto hilang” untuk menautkannya ulang.",
"Applied {name}": "{name} diterapkan",
"Delete “{name}”?": "Hapus “{name}”?",
"Updated {name}": "{name} diperbarui",
"Exported to {path}": "Diekspor ke {path}",
"Saved template {name}": "Templat {name} disimpan",
"Imported {name}": "{name} diimpor",
"Skipped {problem}": "Dilewati: {problem}",
"…and {more} more": "…dan {more} lainnya",
"Template preview: {name}": "Pratinjau templat: {name}",
"{n} photos in view · {slides} slides · {time}": "{n} foto dalam tampilan · {slides} slide · {time}",
"{n} pages": "{n} halaman",
"{n} JPEG files": "{n} file JPEG",
"{s} s of AV1 video": "Video AV1 {s} detik",
"Slideshow exported: {what}": "Tayangan slide diekspor: {what}",
" · {n} skipped (could not be rendered)": " · {n} dilewati (tidak dapat dirender)",
"{n} raws can now be developed from their raw data": "{n} file raw kini dapat diolah dari data raw-nya",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft berhenti tiba-tiba: {e}\n\nDetailnya ada di file log: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft berhenti tiba-tiba: {e}",
"Renames {n} face{} across the library. The files are not changed.": "Mengganti nama {n} wajah{:.0} di seluruh pustaka. File tidak diubah.",
"“{to}” is already a person. Combining them makes one person; Undo separates them again.": "“{to}” sudah terdaftar sebagai orang. Jika digabungkan, keduanya menjadi satu orang; Urungkan akan memisahkannya lagi.",
"{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has": "Hasil penggabungan {what} ditambahkan dengan resolusi {mp}: resolusi penuhnya ({full}) memerlukan memori melebihi kapasitas komputer ini",
"{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "Hasil penggabungan {what} ditambahkan dengan resolusi {mp}, resolusi maksimum untuk panorama (resolusi penuhnya {full})"
}
File diff suppressed because it is too large Load Diff
+41
View File
@@ -132,6 +132,7 @@ language_table! {
Ka, "ka", "ქართული", "Geor", include_str!("../locales/ka.json");
Sv, "sv", "Svenska", "Latn", include_str!("../locales/sv.json");
Fi, "fi", "Suomi", "Latn", include_str!("../locales/fi.json");
Id, "id", "Bahasa Indonesia", "Latn", include_str!("../locales/id.json");
}
// The settings file stores the BCP-47 code (`"zh-hans"`), never the Rust variant name, so a
@@ -558,6 +559,45 @@ mod tests {
set_language(Locale::En);
}
#[test]
fn indonesian_switches_persists_formats_dates_and_preserves_values() {
use crate::control::{ControlRequest, Outcome};
let mut app = crate::LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default());
app.run("app.language.indonesian", serde_json::json!({})).unwrap();
assert_eq!(app.ui.language, Locale::Id);
assert_eq!(Locale::Id.name(), "Bahasa Indonesia");
assert_eq!(Locale::Id.script(), "Latn");
assert_eq!(crate::menubar::checked(&app, "app.language.indonesian"), Some(true));
assert_eq!(tr("Exposure"), "Eksposur");
assert_eq!(tr("My Photos"), "Foto saya");
assert_eq!(date_group_label("2026-09-20", false), "Minggu, 20/09/2026");
assert_eq!(date_group_label("2026-09-20", true), "Minggu, 20");
assert_eq!(date_group_label("2026-09", false), "09/2026");
assert_eq!(date_group_label("2026", false), "2026");
assert_eq!(display_time("2026-09-20T16:04:05"), "Minggu, 20/09/2026 16:04:05");
// Indonesian keeps a noun unchanged after a count ("1 foto", "22 foto"), so the English
// plural suffix is consumed and never shows.
for n in [0, 1, 2, 5, 11, 22] {
assert_eq!(tr_format!("Imported {} photo{}", n, if n == 1 { "" } else { "s" }), format!("{n} foto diimpor"));
}
let name = String::from("Color 1234.56 {title} 日本語");
assert_eq!(tr_format!("Added {n} photo{} to “{}”", "s", name, n = 22), format!("22 foto ditambahkan ke “{name}”"));
assert_eq!(builtin_label("Warm Glow", false), "Warm Glow");
assert_eq!(tr("photo-{title}.jpg"), "photo-{title}.jpg");
let saved = serde_json::to_string(&app.ui).unwrap();
assert!(saved.contains(r#""language":"id""#));
assert_eq!(serde_json::from_str::<crate::state::UiState>(&saved).unwrap().language, Locale::Id);
let ctx = egui::Context::default();
for tag in ["id", "id-ID", "id_ID.UTF-8", "id-Latn-ID"] {
assert_eq!(Locale::parse_tag(tag), Some(Locale::Id));
let (request, _) = ControlRequest::new("ui.set", serde_json::json!({"language": tag}));
let Outcome::Done(reply) = crate::control::handle(&mut app, &ctx, &request) else { panic!("expected reply") };
assert_eq!(reply["ok"], true);
assert_eq!(language(), Locale::Id);
}
set_language(Locale::En);
}
#[test]
fn sentence_case_keeps_georgian_lowercase() {
assert_eq!(upper_first("its input is not an image"), "Its input is not an image");
@@ -870,6 +910,7 @@ mod tests {
("app.language.georgian", Locale::Ka),
("app.language.swedish", Locale::Sv),
("app.language.finnish", Locale::Fi),
("app.language.indonesian", Locale::Id),
];
// One command per language, and every command reachable from the menu table.
assert_eq!(commands.len(), Locale::ALL.len());
+2
View File
@@ -30,6 +30,7 @@ pub const LANGUAGE_COMMANDS: &[UiCommand] = &[
("app.language.georgian", crate::i18n::Locale::Ka.name(), None, "Edit>Language"),
("app.language.swedish", crate::i18n::Locale::Sv.name(), None, "Edit>Language"),
("app.language.finnish", crate::i18n::Locale::Fi.name(), None, "Edit>Language"),
("app.language.indonesian", crate::i18n::Locale::Id.name(), None, "Edit>Language"),
];
/// Every UI command: the languages, then everything else. `xtask parity` reads both tables from
@@ -58,6 +59,7 @@ pub fn language_from_command(id: &str) -> Option<crate::i18n::Locale> {
"app.language.georgian" => Some(crate::i18n::Locale::Ka),
"app.language.swedish" => Some(crate::i18n::Locale::Sv),
"app.language.finnish" => Some(crate::i18n::Locale::Fi),
"app.language.indonesian" => Some(crate::i18n::Locale::Id),
_ => None,
}
}
+2 -2
View File
@@ -621,13 +621,13 @@ mod tests {
"Export Keywords…",
];
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("locales");
for code in ["de", "es", "fr", "pt-br", "ru", "uk", "it", "ka", "ja", "zh-hans", "zh-hant", "pl", "cs", "sv", "fi"] {
for code in ["de", "es", "fr", "pt-br", "ru", "uk", "it", "ka", "ja", "zh-hans", "zh-hant", "pl", "cs", "sv", "fi", "id"] {
let text = std::fs::read_to_string(dir.join(format!("{code}.json"))).unwrap();
let catalog: std::collections::HashMap<String, String> = serde_json::from_str(&text).unwrap();
for label in labels {
assert!(catalog.contains_key(label), "{code}: “{label}”");
}
}
assert_eq!(Locale::ALL.len(), 16, "a language added: list its file above");
assert_eq!(Locale::ALL.len(), 17, "a language added: list its file above");
}
}