i18n: add Finnish (fi) interface language

Adds Finnish catalogs (fi.json, fi-formats.json), the language table entry
and menu command, Finnish tests, docs and an attribution row. What's New text
stays English, as in most catalogs.

AI-assisted translation (Claude Code), reviewed by a native Finnish speaker.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Roope Gröhn
2026-10-20 18:29:23 +03:00
co-authored by Claude Sonnet 5.5
parent a2a41383c4
commit 494d9d77bb
11 changed files with 2799 additions and 10 deletions
+1 -1
View File
@@ -448,7 +448,7 @@ CRAFT_FONTS_DIR=../craft-fonts cargo run --release -p lightcraft
Without it LightCraft builds and runs the same, but Chinese and Japanese text have no glyphs. Fonts are never committed to this
repo — the standard for what goes there lives with it: see the [craft-fonts README](https://github.com/storytold/craft-fonts#readme).
**Interface language:** **Edit → Language** (English, 简体中文, 繁體中文(台灣), 日本語, Português (Brasil), Español, Deutsch, Русский, Français, Українська, Polski, Čeština, Italiano, ქართული) or **Settings → General →
**Interface language:** **Edit → Language** (English, 简体中文, 繁體中文(台灣), 日本語, Português (Brasil), Español, Deutsch, Русский, Français, Українська, Polski, Čeština, Italiano, ქართული, Suomi) or **Settings → General →
Language**; the choice applies immediately and persists. See [docs/localization.md](docs/localization.md).
**Logs:** the desktop app writes its log to standard error and to `logs/lightcraft.log` in its settings folder
+2 -1
View File
@@ -96,7 +96,7 @@ while lower-layer errors stay English. Detail: [localization-parity.md](docs/loc
| Korean | ko | 0% | none (no Hangul face) |
| Vietnamese | vi | 0% | none |
Also shipped: Traditional Chinese (87%), Russian (74%), Ukrainian (99%), Italian (92%), Georgian (100%).
Also shipped: Traditional Chinese (87%), Russian (74%), Ukrainian (99%), Italian (92%), Georgian (100%), Finnish (79%).
## Upcoming
@@ -192,6 +192,7 @@ Ranked toward beta; detail in [docs/roadmap.md](docs/roadmap.md), gaps in [docs/
| 2026-10-12 | minor | Mamiya MEF decoded and imported: ZD verified (241); headline numbers unchanged |
| 2026-10-12 | minor | Epson ERF decoded: 3 more verified models (240); headline numbers unchanged |
| 2026-10-12 | minor | Georgian (`ka`) interface language added (every catalog message) |
| 2026-10-17 | minor | Finnish (`fi`) interface language added: 2,610 of 3,308 messages (79%), locale, menu command and tests; headline numbers unchanged |
| 2026-10-14 | minor | Full-resolution zoom (gap 4) closed: soft proofing gets zoom windows; headline numbers unchanged |
| 2026-10-12 | minor | UI/UX remaining 40–70 h → 34–58 h (accessibility audit landed); headline numbers unchanged |
| 2026-10-14 | minor | Camera-coverage sweep (#1327): 30 models verified on 40 CC0 files (coverage.py 515 → 545 verified); sensor verification does not establish colour parity (#1277); headline numbers unchanged |
+1
View File
@@ -34,6 +34,7 @@ covers a whole directory), or if a licence file referenced in the Licence column
| `crates/ui-egui/locales/cs.json`, `crates/ui-egui/locales/cs-formats.json` | Czech interface catalogs | @Losminek with AI assistance (Codex) | original work: translations of English source phrases, reviewed against the app UI conventions; no external translation material | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 2026-10-11 | catalog coverage, Czech dates and count-neutral wording; independent scope/terminology review; native-speaker proofreading pending |
| `crates/ui-egui/locales/it.json`, `crates/ui-egui/locales/it-formats.json` | Italian interface catalogs | @GabrielePecchioli with Claude Code; PhotoCraft, VectorCraft, PdfCraft and WordCraft translation contributors for reused labels | original translations from English source; 410 initial English-key matches reviewed from [PhotoCraft da3da09](https://github.com/storytold/photocraft/blob/da3da0942c14c1159f40c652afcf33584332c733/crates/ui-egui/src/i18n/it.tsv), [VectorCraft 5432821](https://github.com/storytold/vectorcraft/blob/543282148007bcb5633c493a5374cd26b0339c8d/crates/ui-egui/src/i18n/it.tsv), [PdfCraft e8e275c](https://github.com/storytold/pdfcraft/blob/e8e275cbd40ccd0dc6b56f82b16a575571043702/crates/ui-egui/src/i18n/it.tsv) and the Italian WordCraft catalog ([storytold/wordcraft#598](https://github.com/storytold/wordcraft/pull/598)); 46 of them retranslated where the sibling's meaning didn't fit a photo app | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`, also the source repositories' licences) | 2026-10-12 | adapted to LightCraft photo-development context, count-neutral labels for Italian plural agreement, reviewed placeholders |
| `crates/ui-egui/locales/ka.json`, `crates/ui-egui/locales/ka-formats.json` | Georgian interface catalogs | Davit Matchakhelidze with Claude; WordCraft, GridCraft, PhotoCraft, FilmCraft and VectorCraft Georgian catalogs for reused labels | original translations from the English source (meaning-based, no vendor localization used); 450 English-key matches reused from the sibling Craft apps' Georgian catalogs | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 2026-10-12 | adapted to LightCraft photo-library and raw-development context; count-neutral numerals; placeholders checked |
| `crates/ui-egui/locales/fi.json`, `crates/ui-egui/locales/fi-formats.json` | Finnish interface catalogs | @roopeg with Claude Code (AI-assisted; native-speaker review by @roopeg) | original work: translations of the English source phrases, using common Finnish photo-editing vocabulary; no external translation material | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 2026-10-17 | count-neutral labels for Finnish partitive agreement, Finnish dates and times (`20.9.2026`, `16:04`), reviewed placeholders |
Notes
- Fonts authored or published by Adobe (Source Sans/Serif/Code, Source Han, …) are not used, even though some are
+299
View File
@@ -0,0 +1,299 @@
{
" ({unsure} with several look-alike files: use Locate)": " ({unsure} usealla samannäköisellä tiedostolla: käytä Paikanna)",
" ({} in Recently Deleted)": " ({} Äskettäin poistetuissa)",
" in {g} group{}": " · ryhmiä: {g}{:.0}",
" · kept, not applied: {}": " · säilytetty, ei käytetty: {}",
" · {:.0}% clipped": " · {:.0}% leikkautunut",
" · {failed} couldn't be rendered": " · renderöinti epäonnistui: {failed}",
" · {n} skipped (could not be rendered)": " · {n} ohitettu (renderöinti ei onnistunut)",
" · {queued} waiting": " · odottaa: {queued}",
" · {skipped} skipped (file exists)": " · {skipped} ohitettu (tiedosto on olemassa)",
" · {} duplicate{} skipped": " · ohitettuja kaksoiskappaleita: {}{:.0}",
" · {} failed: {}": " · {} epäonnistui: {}",
" · {} moved": " · {} siirretty",
" · {} not readable": " · {} ei luettavissa",
" · {} original{} left at the source": " · alkuperäisiä jäi lähteeseen: {}{:.0}",
" · {} restored from Recently Deleted": " · {} palautettu Äskettäin poistetuista",
" — Capture One won't import a list with ; , < or > in a keyword: {names}": " — Capture One ei tuo luetteloa, jonka avainsanassa on ; , < tai >: {names}",
" — kept but not applied: {}": " — säilytetty mutta ei käytetty: {}",
" — left out, as a keyword list can't hold them: {names}": " — jätetty pois, koska avainsanaluettelo ei voi sisältää niitä: {names}",
" — not carried over: {}{}": " — ei siirretty: {}{}",
"+{n} more": "+{n} lisää",
"+{} more": "+{} lisää",
", {failed} file{} not readable": ", ei luettavissa olevia tiedostoja: {failed}{:.0}",
", {} label": ", {} merkintä",
", {} star{}": ", tähtiä {}{:.0}",
"1 task is still running: {names}. Quitting stops it.": "1 tehtävä on yhä käynnissä: {names}. Lopettaminen pysäyttää sen.",
"; {kept} already had a location": "; jo sijaintitiedolliset: {kept}",
"; {n} gained synonyms": "; {n} sai synonyymejä",
"; {outside} outside its time range": "; {outside} aika-alueen ulkopuolella",
"@{}: {} PRs, {} commits, +{} / −{} lines (Δ {}), +{} / −{} binary assets, {} – {}": "@{}: PR:t {}, commitit {}, rivit +{} / −{} (Δ {}), binääriresurssit +{} / −{}, {} – {}",
"Add {local_n} to My Photos": "Lisää {local_n} Omiin kuviin",
"Add {} Photo{}": "Lisää kuvia: {}{:.0}",
"Add {} Photo{}, restore {}": "Lisää kuvia: {}{:.0}, palauta {}",
"Add “{}” to Favorites": "Lisää ”{}” suosikkeihin",
"Added {n} keyword{}": "Lisättyjä avainsanoja: {n}{:.0}",
"Added {n} photo{} to My Photos": "Omiin kuviin lisättyjä kuvia: {n}{:.0}",
"Added {n} photo{} to “{}”": "Lisättyjä kuvia kohteeseen ”{1}”: {n}{0:.0}",
"Added {what} to {name}": "{what} lisätty kohteeseen {name}",
"Added “{keyword}” to {n} photo{}": "Avainsana ”{keyword}” lisätty kuviin: {n}{:.0}",
"Adding photos… {} of {}": "Lisätään kuvia… {} / {}",
"All {n} selected photos move by the same amount.": "Kaikkia valittuja kuvia ({n}) siirretään saman verran.",
"Already in the library, in Recently Deleted: {} photo{} selected · right-click ▸ Restore, or Delete Permanently to import afresh": "Jo kirjastossa, Äskettäin poistetuissa: valittuja kuvia {}{:.0} · napsauta hiiren kakkospainikkeella → Palauta tai Poista pysyvästi tuodaksesi uudelleen",
"Already in the library: {} photo{} selected in All Photos": "Jo kirjastossa (Kaikki kuvat): valittuja kuvia {}{:.0}",
"Already in “{}”": "Jo kohteessa ”{}”",
"Applied {name}": "{name} käytetty",
"Apply to “{mask}” on Selected Photos": "Käytä maskiin ”{mask}” valituissa kuvissa",
"Auto ({})": "Auto ({})",
"Auto Import: added {} photo{}": "Automaattinen tuonti: lisättyjä kuvia {}{:.0}",
"Auto Sync: edits apply to {n} photos": "Automaattinen synkronointi: muokkaukset koskevat kuvia: {n}",
"Automatic ({} MB)": "Automaattinen ({} Mt)",
"Build 1:1 Previews ({scope})": "Luo 1:1-esikatselut ({scope})",
"Build Smart Previews ({scope})": "Luo älyesikatselut ({scope})",
"Build Standard-Sized Previews ({scope})": "Luo vakiokokoiset esikatselut ({scope})",
"Building previews for {} photos…": "Luodaan esikatseluja {} kuvalle…",
"Click {}:1": "Napsautus {}:1",
"Color space: Rec. 2020 {curve} (HDR AVIF)": "Väriavaruus: Rec. 2020 {curve} (HDR AVIF)",
"Couldn't open the editor: {e}": "Muokkausohjelmaa ei voitu avata: {e}",
"Create Keyword Tag Inside “{name}”…": "Luo avainsanatunniste kohteen ”{name}” sisään…",
"Create the keyword “{name}”": "Luo avainsana ”{name}”",
"Create {n} Virtual Copies": "Luo {n} virtuaalikopiota",
"Creates {} stacks from {} of {scope}": "Luo pinoja: {} · kuvia: {} ({scope})",
"Decoder: {reason}": "Purkaja: {reason}",
"Delete \"{label}\"": "Poista ”{label}”",
"Delete \"{label}\" (and the mask)": "Poista ”{label}” (ja maski)",
"Delete the effect preset “{name}”?": "Poistetaanko tehosteesiasetus ”{name}”?",
"Delete the keyword “{name}”?": "Poistetaanko avainsana ”{name}”?",
"Delete these {n} effect presets?": "Poistetaanko nämä {n} tehosteesiasetusta?",
"Delete {n} Photos": "Poista {n} kuvaa",
"Delete {n} effect preset{}? This can't be undone.": "Poistetaanko tehosteesiasetuksia: {n}{:.0}? Tätä ei voi kumota.",
"Delete “{current}”": "Poista ”{current}”",
"Delete “{name}”?": "Poistetaanko ”{name}”?",
"Denoising {name}: {done} of {total} tiles{}": "Kohinanpoisto {name}: {done} / {total} ruutua{}",
"Discard Smart Previews ({scope})": "Hylkää älyesikatselut ({scope})",
"Done ({what}): {done} photo{plural}": "Valmis ({what}): kuvia {done}{plural:.0}",
"Downloads from {host}. Once it has arrived and checked out, LightCraft installs it, {after}.": "Latautuu osoitteesta {host}. Kun se on saapunut ja tarkistettu, LightCraft asentaa sen, {after}.",
"Duplicate \"{label}\"": "Monista ”{label}”",
"Effect: {name}": "Tehoste: {name}",
"Example: {example}": "Esimerkki: {example}",
"Exported to {path}": "Viety kohteeseen {path}",
"Exported {n} keyword{}": "Viety avainsanoja: {n}{:.0}",
"Exported {n} photo{}": "Viety kuvia: {n}{:.0}",
"Exported {ok} of {total} photo{}": "Viety kuvia: {ok} / {total}{:.0}",
"Exported {} effect preset{}": "Viety tehosteesiasetuksia: {}{:.0}",
"Exported {} point curve preset{}": "Viety pistekäyrän esiasetuksia: {}{:.0}",
"Exported {} preset{}": "Viety esiasetuksia: {}{:.0}",
"Exporting {} of {total}": "Viedään {} / {total}",
"Filter bar — {active} active filter{}": "Suodatinpalkki — aktiivisia suodattimia: {active}{:.0}",
"Filtered: {label}": "Suodatettu: {label}",
"Focus {:.0}": "Tarkennus {:.0}",
"Folder “{}” (and its subfolders)": "Kansio ”{}” (ja sen alikansiot)",
"Found {n} missing photo{}; {left} still missing{unsure}": "Löydettyjä puuttuvia kuvia: {n}{:.0}; edelleen puuttuu: {left}{unsure}",
"Healed {n} dust spot{}": "Korjattuja pölyhiukkasia: {n}{:.0}",
"Import cancelled · {} photo{} added": "Tuonti peruttu · lisättyjä kuvia: {}{:.0}",
"Import failed: {e}": "Tuonti epäonnistui: {e}",
"Import warnings ({n}):\n{warnings}": "Tuontivaroitukset ({n}):\n{warnings}",
"Imported {e} mask effect{} (Masking ▸ Effect)": "Tuotuja maskitehosteita: {e}{:.0} (Maskaus → Tehoste)",
"Imported {name}": "{name} tuotu",
"Imported {n} point curve preset{}": "Tuotuja pistekäyrän esiasetuksia: {n}{:.0}",
"Imported {n} preset{}": "Tuotuja esiasetuksia: {n}{:.0}",
"Imported {n} preset{}, {f} file{} not readable": "Tuotuja esiasetuksia: {n}{:.0}, ei luettavissa olevia tiedostoja: {f}{:.0}",
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "Tuotuja profiileja: {profiles}{:.0} (Profiiliselain → niiden ryhmät)",
"Imported {} photo{}": "Tuotuja kuvia: {}{:.0}",
"In line for AI Denoise ({ahead} ahead)…": "Jonossa tekoälykohinanpoistoon ({ahead} edellä)…",
"Insert at the cursor (also written {})": "Lisää kohdistimen kohtaan (kirjoitetaan myös {})",
"It is taken off {count} photo{}, with the keywords below it. Undo brings it back.": "Se poistetaan {count} kuvalta{:.0}, alla olevat avainsanat mukaan lukien. Kumoa palauttaa sen.",
"Keywords of {n} selected photos": "Valittujen kuvien ({n}) avainsanat",
"Label Set: {name}": "Merkintäjoukko: {name}",
"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 ei vielä osaa purkaa tätä raw-muunnelmaa ({}). Muokkaat kameran upotettua JPEG-esikatselua, joka sisältää jo kameran kuvatyylin (esim. Monochrome) ja valkotasapainon.",
"LightCraft could not open its window: {e}": "LightCraft ei voinut avata ikkunaansa: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft ei voinut avata ikkunaansa: {e}\n\nLokitiedostossa on lisätietoja: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft pysähtyi odottamatta: {e}",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft pysähtyi odottamatta: {e}\n\nLokitiedostossa on lisätietoja: {path}",
"Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Lightroom-katalogi tarkastettu: kuvia {photos}, kokoelmia {collections}, puuttuvia {missing}",
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "Lightroomin geometriaa ei säilytetty kohteelle {path}; Ohjatun Uprightin tallennettua muunnosta ei tueta.",
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Lightroomin geometriaa ei säilytetty kohteelle {path}; tallennettua Upright-muunnosta ei tueta.",
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Lightroomin geometriaa ei säilytetty kohteelle {path}; tallennettu muunnos ohitetaan, koska Upright on pois päältä.",
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Lightroomin geometriaa ei säilytetty kohteelle {path}; {mode} lasketaan uudelleen.",
"Lightroom import complete: {imported} photos added": "Lightroom-tuonti valmis: lisättyjä kuvia {imported}",
"Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom-tuonti valmis: lisättyjä kuvia {imported}, epäonnistuneita {failed}",
"Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom-tuonti valmis: lisättyjä kuvia {imported}; hakemistovaroitus: {warning}",
"Lightroom import failed: {e}{warning}": "Lightroom-tuonti epäonnistui: {e}{warning}",
"Lightroom inspection failed: {e}{warning}": "Lightroom-tarkastelu epäonnistui: {e}{warning}",
"MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Fontit: {} (OFL). Kuvakkeet: omat.",
"Making the denoised picture… {}%": "Tehdään kohinanpoistettua kuvaa… {}%",
"Matches: {}": "Täsmäävät: {}",
"Merging… {stage} {:.0}%": "Yhdistetään… {stage} {:.0}%",
"Move {n} photo{} to “{}”": "Siirrä kuvia kohteeseen ”{1}”: {n}{0:.0}",
"Move {n} photo{} to “{}”?": "Siirretäänkö kuvat kohteeseen ”{1}” ({n}){0:.0}?",
"Move {what} to Recently Deleted?": "Siirretäänkö {what} Äskettäin poistettuihin?",
"Moved {n} photo{} to “{}”. Edit → Undo moves them back.": "Siirretty kuvia kohteeseen ”{1}”: {n}{0:.0}. Muokkaa → Kumoa siirtää ne takaisin.",
"Moved {} photo(s)": "Siirretty kuvia: {}",
"Moved; {} photo(s) relinked": "Siirretty; yhdistetty uudelleen kuvia: {}",
"Named ({named_n})": "Nimetyt ({named_n})",
"New Folder in “{name}”": "Uusi kansio kohteessa ”{name}”",
"On {have} of {of} selected photos": "{have} / {of} valitulla kuvalla",
"Painting “{k}”: click photos to add or remove it · Esc stops": "Maalataan ”{k}”: napsauta kuvia lisätäksesi tai poistaaksesi sen · Esc lopettaa",
"Paste into {n} photo{} — only settings that were copied are pasted": "Liitä kuviin: {n}{:.0} — vain kopioidut asetukset liitetään",
"Preset: {name}": "Esiasetus: {name}",
"Preview only — editing the camera's embedded JPEG ({})": "Vain esikatselu — muokataan kameran upotettua JPEG-kuvaa ({})",
"Preview only — {}": "Vain esikatselu — {}",
"Previews ready for {done} photo{plural}": "Esikatselut valmiina {done} kuvalle{plural:.0}",
"Profile: {}": "Profiili: {}",
"Proof Copy ({})": "Vedoskopio ({})",
"Proof Preview · {}": "Vedoksen esikatselu · {}",
"Put inside “{parent}”": "Laita kohteen ”{parent}” sisään",
"Quick Develop · {n} photos": "Pikakehitys · kuvia: {n}",
"Quick Develop: {name}": "Pikakehitys: {name}",
"Rated {}": "Luokitus {}",
"Reading Lightroom catalog… {done} of {total}": "Luetaan Lightroom-katalogia… {done} / {total}",
"Reading photos… {done} of {total}": "Luetaan kuvia… {done} / {total}",
"Reading photos… {} of {}": "Luetaan kuvia… {} / {}",
"Redo {}": "Tee uudelleen {}",
"Remove “{name}”": "Poista ”{name}”",
"Remove “{name}” and its {count} photo{} from the library?": "Poistetaanko ”{name}” ja sen kuvat ({count}{:.0}) kirjastosta?",
"Remove “{}” from Favorites": "Poista ”{}” suosikeista",
"Removed from {}": "Poistettu kohteesta {}",
"Removed {what} from {name}": "{what} poistettu kohteesta {name}",
"Removed “{name}” from {n} photos. Edit ▸ Undo puts it back.": "”{name}” poistettu {n} kuvalta. Muokkaa → Kumoa palauttaa sen.",
"Rename {n} Photos…": "Nimeä uudelleen {n} kuvaa…",
"Rename “{name}”": "Nimeä uudelleen ”{name}”",
"Renames {n} face{} across the library. The files are not changed.": "Nimeää uudelleen kasvoja koko kirjastossa: {n}{:.0}. Tiedostoja ei muuteta.",
"Renames “{from}” on {n} photo{} (keywords below it too). Use | for levels, e.g. Travel|Italy. An existing name merges the two.": "Nimeää ”{from}” uudelleen {n} kuvassa{:.0} (myös sen alla olevat avainsanat). Käytä |-merkkiä tasojen erottamiseen, esim. Matka|Italia. Olemassa oleva nimi yhdistää nämä kaksi.",
"Rendering on the CPU: {why}": "Renderöidään suorittimella: {why}",
"Replace {} with:": "Korvaa {} seuraavalla:",
"Reset {name}": "Nollaa {name}",
"Reset {} Channel": "Nollaa kanava {}",
"Restore {}": "Palauta {}",
"Restore {} Photo{}": "Palauta kuvia: {}{:.0}",
"Restored {} photo{} from Recently Deleted": "Äskettäin poistetuista palautettu kuvia: {}{:.0}",
"Running on the processor: {}.": "Käytössä suoritin: {}.",
"Running on {}.": "Käytössä: {}.",
"Saved export preset “{}”": "Vientiesiasetus ”{}” tallennettu",
"Saved template {name}": "Mallipohja {name} tallennettu",
"Scores {scope} for focus and exposure and finds similar shots taken within seconds of each other (bursts).": "Pisteyttää {scope} tarkennuksen ja valotuksen mukaan ja etsii samankaltaiset otokset, jotka on otettu sekuntien sisällä toisistaan (sarjat).",
"Show {hidden} hidden location{}": "Näytä piilotettuja sijainteja: {hidden}{:.0}",
"Showing {n} of {}": "Näytetään {n} / {}",
"Shows the model's terms, then downloads {} from {host}, installs it and starts using it. Nothing else is sent.": "Näyttää mallin ehdot, lataa sitten {} osoitteesta {host}, asentaa sen ja ottaa sen käyttöön. Mitään muuta ei lähetetä.",
"Skipped {problem}": "Ohitettu {problem}",
"Slideshow exported: {what}": "Diaesitys viety: {what}",
"Smart album: {}": "Älyalbumi: {}",
"Tagged {n} photo{} from the tracklog": "Reittilokista merkittyjä kuvia: {n}{:.0}",
"Template preview: {name}": "Mallipohjan esikatselu: {name}",
"The SAM 3 download failed: {e}": "SAM 3:n lataus epäonnistui: {e}",
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "Kuvien XMP-sivutiedostoja ei voitu kirjoittaa ({n}; onko niiden kansio offline-tilassa?). LightCraft yrittää edelleen.",
"There is a keyword set “{name}” already": "Avainsanajoukko ”{name}” on jo olemassa",
"There is a “{name}” at the top level already. Merge the two?": "Ylimmällä tasolla on jo ”{name}”. Yhdistetäänkö ne?",
"Trained on: {provenance}": "Koulutettu aineistolla: {provenance}",
"Undo {}": "Kumoa {}",
"Unknown tag{s} {} stay{} as typed (see Tags)": "Tuntemattomat tunnisteet{s:.0} {} pysyvät sellaisinaan{:.0} (katso Tunnisteet)",
"Update “{name}” with Current Settings": "Päivitä ”{name}” nykyisillä asetuksilla",
"Updated {name}": "{name} päivitetty",
"Updated “{name}”": "”{name}” päivitetty",
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "Versio {} — clean-room-menetelmällä tehty, puhtaasti Rustilla toteutettu kuvakirjasto ja raw-kehitin.",
"Version: {}": "Versio: {}",
"Virtual copy “{name}” of {of}": "Virtuaalikopio ”{name}” kuvasta {of}",
"Working on {what} for {} photos…": "Käsitellään {what} {} kuvalle…",
"XMP adjustments were not imported for {path}: {fields}.": "XMP-säätöjä ei tuotu kohteelle {path}: {fields}.",
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "XMP-profiilia ”{profile}” ei käytetty kohteelle {path}; värit voivat poiketa Lightroomista.",
"new, inside {parent}": "uusi, kohteen {parent} sisällä",
"none (CPU rendering): {why}": "ei mitään (suoritinrenderöinti): {why}",
"not added to Apple Photos: {e}": "ei lisätty Apple Kuviin: {e}",
"the {n} selected photos": "valitut kuvat ({n})",
"the {} photos in view": "näkymän kuvat ({})",
"these {count} photos": "nämä kuvat ({count})",
"{:.1} MB": "{:.1} Mt",
"{count} more warnings.": "Varoituksia lisäksi: {count}.",
"{count} smart preview{} · {:.1} MB": "Älyesikatseluita: {count}{:.0} · {:.1} Mt",
"{done} of {total}": "{done} / {total}",
"{done} of {total} MB": "{done} / {total} Mt",
"{file} · {size}": "{file} · {size}",
"{f} folders (and their subfolders)": "Kansioita: {f} (ja niiden alikansiot)",
"{label} Grading": "{label}: väriluokittelu",
"{label} {d:+} on every selected photo": "{label} {d:+} jokaisessa valitussa kuvassa",
"{label} {index} of {n}": "{label} {index} / {n}",
"{label}, {n} photos": "{label}, kuvia: {n}",
"{matching} of {t} photos": "Kuvia: {matching} / {t}",
"{matching} photos": "Kuvia: {matching}",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "Puuttuvia alkuperäisiä: {missing}. Niiden katalogitietueet ovat käytettävissä; yhdistä ne uudelleen komennolla Etsi puuttuvat kuvat.",
"{mode} spot {} of {}": "{mode} täplä {} / {}",
"{month}": "{month}",
"{month} {year}": "{month}/{year}",
"{msg} ({failed} failed)": "{msg} ({failed} epäonnistui)",
"{name} Label": "Merkintä: {name}",
"{name} Off": "{name} pois",
"{name} On": "{name} päällä",
"{name} opened for editing; it is stacked with the original": "{name} avattu muokattavaksi; se pinotaan alkuperäisen kanssa",
"{name} opened in the other editor": "{name} avattu toisessa muokkausohjelmassa",
"{name} · {terms}": "{name} · {terms}",
"{name} — double-click to reset it": "{name} — kaksoisnapsauta nollataksesi sen",
"{n} JPEG files": "JPEG-tiedostoja: {n}",
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "Ohitettuja paikallisia kuvia: {n}{:.0}: albumit sisältävät kirjaston kuvia (lisää ensin Omiin kuviin)",
"{n} added to Apple Photos": "{n} lisätty Apple Kuviin",
"{n} added to Apple Photos album “{album}”": "{n} lisätty Apple Kuvien albumiin “{album}”",
"{n} change{} couldn't be written to disk: {e}\nIf you quit now, {} lost.": "Muutoksia, joita ei voitu kirjoittaa levylle: {n}{:.0}: {e}\nJos lopetat nyt, menetät ne.{:.0}",
"{n} change{} saved in memory but not written to disk: {e}\nLightCraft retries automatically; quitting now would lose {}.": "Muistiin tallennettuja, levylle kirjoittamattomia muutoksia: {n}{:.0}: {e}\nLightCraft yrittää automaattisesti uudelleen; nyt lopettaminen menettäisi ne.{:.0}",
"{n} change{} saved in memory but not written to disk: {e} — LightCraft will retry": "Muistiin tallennettuja, levylle kirjoittamattomia muutoksia: {n}{:.0}: {e} — LightCraft yrittää uudelleen",
"{n} found · {sel} selected": "Löytyi: {n} · valittu: {sel}",
"{n} originals opened in the other editor": "Toisessa muokkausohjelmassa avattuja alkuperäisiä: {n}",
"{n} pages": "Sivuja: {n}",
"{n} photos": "Kuvia: {n}",
"{n} photos in view · {slides} slides · {time}": "Kuvia näkymässä: {n} · dioja: {slides} · {time}",
"{n} photos in {} are hidden by the filters above": "Yllä olevat suodattimet piilottavat kuvia kohteessa {}: {n}",
"{n} photos opened for editing; each is stacked with its original": "Muokattavaksi avattuja kuvia: {n}; kukin pinotaan alkuperäisensä kanssa",
"{n} photo{}": "Kuvia: {n}{:.0}",
"{n} photo{} match · updates automatically as photos change": "Täsmääviä kuvia: {n}{:.0} · päivittyy automaattisesti kuvien muuttuessa",
"{n} photo{} now · updates automatically as photos change": "Kuvia nyt: {n}{:.0} · päivittyy automaattisesti kuvien muuttuessa",
"{n} preset{}": "Esiasetuksia: {n}{:.0}",
"{n} raws can now be developed from their raw data": "Raw-kuvia, jotka voidaan nyt kehittää raakadatastaan: {n}",
"{n} selected photos · {pages} pages": "Valittuja kuvia: {n} · sivuja: {pages}",
"{n} similar photo{} · View ▸ Clear Filters to see all": "Samankaltaisia kuvia: {n}{:.0} · Näytä → Tyhjennä suodattimet näyttääksesi kaikki",
"{n} tasks are still running: {names}. Quitting stops them.": "Käynnissä olevia tehtäviä: {n}: {names}. Lopettaminen pysäyttää ne.",
"{n} {noun}": "{n} {noun}",
"{pct} %": "{pct} %",
"{photos} catalog records processed; {collections} collections.": "Käsiteltyjä katalogitietueita: {photos}; kokoelmia: {collections}.",
"{pid} (missing)": "{pid} (puuttuu)",
"{sel_n} selected · {counted}": "Valittu: {sel_n} · {counted}",
"{size} GB": "{size} Gt",
"{stage}… {:.0}%": "{stage}… {:.0}%",
"{s} s of AV1 video": "{s} s AV1-videota",
"{verb} {n} Photo{}": "{verb} kuvia: {n}{:.0}",
"{weekday}, {day}": "{weekday} {day}.",
"{weekday}, {day} {month} {year}": "{weekday} {day}.{month}.{year}",
"{what} merge added": "{what}: yhdistetty kuva lisätty",
"{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "{what}: yhdistetty kuva lisätty koossa {mp}, joka on suurin mahdollinen panoraama (täysi koko on {full})",
"{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has": "{what}: yhdistetty kuva lisätty koossa {mp}: sen täysi koko, {full}, vaatisi enemmän muistia kuin tällä tietokoneella on",
"{what} merge cancelled": "{what}: yhdistäminen peruttu",
"{what} merge failed: {e}": "{what}: yhdistäminen epäonnistui: {e}",
"{year}": "{year}",
"{{date:…}} directives: {}": "{{date:…}}-ohjausmerkit: {}",
"{} KB": "{} kt",
"{} MB": "{} Mt",
"{} and higher": "{} ja ylöspäin",
"{} contributors · {} commits": "Tekijöitä: {} · committeja: {}",
"{} failed": "{} epäonnistui",
"{} files": "Tiedostoja: {}",
"{} files and {f} folder{}": "Tiedostoja: {}, kansioita: {f}{:.0}",
"{} item{}": "Kohteita: {}{:.0}",
"{} label (click more labels to show any of them)": "Merkintä: {} (napsauta lisää merkintöjä näyttääksesi minkä tahansa niistä)",
"{} of 4 guides — drag along lines that should be vertical or horizontal.": "{} / 4 apuviivaa — vedä pysty- tai vaakasuoraksi tarkoitettujen viivojen suuntaisesti.",
"{} of {} from {host}": "{} / {} kohteesta {host}",
"{} photos": "Kuvia: {}",
"{} photos · exposures {} EV": "Kuvia: {} · valotukset {} EV",
"{} photos · {} · {:.0}° × {:.0}°": "Kuvia: {} · {} · {:.0}° × {:.0}°",
"{} photo{} not readable": "Ei luettavissa olevia kuvia: {}{:.0}",
"{} sample{}": "Näytteitä: {}{:.0}",
"{} sec": "{} s",
"{} spot(s) on this photo": "Täpliä tässä kuvassa: {}",
"{} · {}. Kept in the library's “denoise” folder; the oldest ones that no photo uses go first when the limit is reached.": "{} · {}. Säilytetään kirjaston ”denoise”-kansiossa; vanhimmat, joita mikään kuva ei käytä, poistuvat ensin, kun raja tulee vastaan.",
"{} — show their photos": "{} — näytä niiden kuvat",
"· {dups} already in the library": "· {dups} jo kirjastossa",
"· {in_source} identical copies": "· identtisiä kopioita: {in_source}",
"· {n} photo{}": "· kuvia: {n}{:.0}",
"· {trashed} in Recently Deleted": "· {trashed} Äskettäin poistetuissa",
"“{parent}” has a “{name}” already. Merge the two?": "Kohteessa ”{parent}” on jo ”{name}”. Yhdistetäänkö ne?",
"“{to}” is already a person. Combining them makes one person; Undo separates them again.": "”{to}” on jo henkilö. Yhdistäminen tekee kahdesta yhden henkilön; Kumoa erottaa ne uudelleen.",
"• {assumption}": "• {assumption}",
"… and {more} more": "… ja {more} lisää",
"…and {more} more": "…ja {more} lisää"
}
File diff suppressed because it is too large Load Diff
+108 -2
View File
@@ -130,6 +130,7 @@ language_table! {
Cs, "cs", "Čeština", "Latn", include_str!("../locales/cs.json");
It, "it", "Italiano", "Latn", include_str!("../locales/it.json");
Ka, "ka", "ქართული", "Geor", include_str!("../locales/ka.json");
Fi, "fi", "Suomi", "Latn", include_str!("../locales/fi.json");
}
// The settings file stores the BCP-47 code (`"zh-hans"`), never the Rust variant name, so a
@@ -790,6 +791,7 @@ mod tests {
("app.language.czech", Locale::Cs),
("app.language.italian", Locale::It),
("app.language.georgian", Locale::Ka),
("app.language.finnish", Locale::Fi),
];
// One command per language, and every command reachable from the menu table.
assert_eq!(commands.len(), Locale::ALL.len());
@@ -1172,6 +1174,110 @@ mod tests {
set_language(Locale::En);
}
/// Finnish's coverage of every catalog message and display label, reported like Italian's.
#[test]
fn finnish_coverage_of_catalogs_commands_controls_and_rules_is_reported() {
let catalog = Locale::Fi.catalog();
let keys: std::collections::BTreeSet<&String> = Locale::ALL.iter().flat_map(|language| language.catalog().keys()).collect();
let lacking: Vec<_> = keys.into_iter().filter(|key| !catalog.contains_key(key.as_str())).collect();
if !lacking.is_empty() {
eprintln!("fi lacks {} catalog message(s) (shown in English): {lacking:?}", lacking.len());
}
let mut labels: Vec<&str> = lightcraft_engine::command_specs().iter().map(|spec| spec.label).collect();
labels.extend(crate::menus::ui_commands().map(|command| command.1).filter(|label| !Locale::ALL.iter().any(|locale| locale.name() == *label)));
labels.extend(lightcraft_develop::CONTROLS.iter().map(|control| control.label));
labels.extend(crate::panels::settings::TABS.iter().map(|(_, label)| *label));
for (_, label, kind) in lightcraft_catalog::rules::FIELDS {
labels.push(label);
labels.extend(lightcraft_catalog::rules::ops_for(*kind).iter().map(|(_, label)| *label));
}
labels.extend(lightcraft_engine::rename::TOKENS.iter().map(|token| token.meaning));
labels.extend(lightcraft_engine::rename::TEMPLATE_NOTES);
let missing: Vec<_> = labels.into_iter().filter(|label| !catalog.contains_key(*label)).collect();
if !missing.is_empty() {
eprintln!("fi lacks {} display label(s) (shown in English): {missing:?}", missing.len());
}
}
#[test]
fn finnish_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.finnish", serde_json::json!({})).unwrap();
assert_eq!(app.ui.language, Locale::Fi);
assert_eq!(crate::menubar::checked(&app, "app.language.finnish"), Some(true));
assert_eq!(tr("Light"), "Valo");
assert_eq!(tr("Landscape"), "Maisema");
assert_eq!(tr("Home"), "Kotikansio");
// Finnish dates have no leading zeros and a lower-case weekday; times are 24-hour with colons.
assert_eq!(date_group_label("2026-09-20", false), "sunnuntai 20.9.2026");
assert_eq!(date_group_label("2026-09-20", true), "sunnuntai 20.");
assert_eq!(date_group_label("2026-09", false), "9/2026");
assert_eq!(date_group_label("2026-09", true), "9");
assert_eq!(date_group_label("2026", false), "2026");
assert_eq!(display_time("2026-09-20T16:04:05"), "sunnuntai 20.9.2026 16:04:05");
// A noun after a numeral is in the partitive singular (1 kuva, 2 kuvaa), which the English
// plural suffix cannot choose, so counts follow a label; the suffix is consumed and never shows.
for n in [0, 1, 2, 5, 11, 21, 22, 25, 111] {
assert_eq!(tr_format!("Imported {} photo{}", n, if n == 1 { "" } else { "s" }), format!("Tuotuja kuvia: {n}"));
assert_eq!(tr_format!("{} files and {f} folder{}", n, "s", f = n), format!("Tiedostoja: {n}, kansioita: {n}"));
let noun = tr(if n == 1 { "cached picture" } else { "cached pictures" });
let expected = if n == 1 { "1 välimuistissa oleva kuva".to_string() } else { format!("{n} välimuistissa olevaa kuvaa") };
assert_eq!(tr_format!("{n} {noun}", n = n, noun = noun), expected);
}
// This trailing argument is queue progress, not an English plural suffix.
let queued = tr_format!(" · {queued} waiting", queued = 5);
assert_eq!(
tr_format!("Denoising {name}: {done} of {total} tiles{}", queued, name = "IMG.CR3", done = 1, total = 4),
"Kohinanpoisto IMG.CR3: 1 / 4 ruutua · odottaa: 5"
);
let name = "Color {title} 日本語";
assert_eq!(tr_format!("Added {n} photo{} to “{}”", "s", name, n = 22), format!("Lisättyjä kuvia kohteeseen ”{name}”: 22"));
assert_eq!(builtin_label("Warm Glow", false), "Warm Glow");
assert_eq!(builtin_label("Warm Glow", true), "Lämmin hehku");
assert_eq!(tr("photo-{title}.jpg"), "photo-{title}.jpg");
assert_eq!(tr("develop.set"), "develop.set");
let saved = serde_json::to_string(&app.ui).unwrap();
assert!(saved.contains(r#""language":"fi""#));
assert_eq!(serde_json::from_str::<crate::state::UiState>(&saved).unwrap().language, Locale::Fi);
let ctx = egui::Context::default();
for code in ["fi", "fi-FI", "fi_FI.UTF-8", "fi-Latn-FI"] {
assert_eq!(Locale::parse_tag(code), Some(Locale::Fi));
let (request, _) = ControlRequest::new("ui.set", serde_json::json!({"language": code}));
let Outcome::Done(reply) = crate::control::handle(&mut app, &ctx, &request) else { panic!("expected reply") };
assert_eq!(reply["ok"], true);
assert_eq!(language(), Locale::Fi);
}
set_language(Locale::En);
}
#[test]
fn finnish_panels_dialogs_and_letters_paint_with_bundled_fonts() {
let ctx = fonts_ctx(&[]);
let mut app = crate::LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default());
app.ui.left_panel = true;
let text = painted_text(&ctx, &mut app, Locale::Fi);
assert!(text.contains("Omat kuvat") && text.contains("Kaikki kuvat"), "{text}");
for (command, title) in [("app.about", "Tietoja LightCraftista"), ("app.shortcuts", "Pikanäppäimet"), ("app.settings", "Asetukset")] {
app.ui.dialog = None;
app.run(command, serde_json::json!({})).unwrap();
let text = painted_text(&ctx, &mut app, Locale::Fi);
assert!(text.contains(title), "{command}: {text}");
}
ctx.fonts_mut(|fonts| {
for family in [egui::FontFamily::Proportional, egui::FontFamily::Name(crate::theme::FONT_SEMIBOLD.into())] {
let font = egui::FontId::new(13.0, family);
for ch in "äöåÄÖÅ”–"
.chars()
.chain(Locale::Fi.catalog().values().flat_map(|text| text.chars()).filter(|ch| ('\u{00c0}'..='\u{017f}').contains(ch)))
{
assert!(fonts.has_glyph(&font, ch), "missing Finnish glyph {ch}");
}
}
});
set_language(Locale::En);
}
/// Every command, control, rule and rename label, and every line of What's New, is a message a
/// catalog can translate. What a language lacks is reported, not failed (like
/// `catalogs_agree_on_placeholders_and_report_gaps`): a feature PR doesn't have to ship every
@@ -1313,7 +1419,7 @@ mod tests {
}
#[test]
fn german_ukrainian_and_italian_buttons_fit_the_bottom_bar_and_export_dialog() {
fn german_ukrainian_italian_and_finnish_buttons_fit_the_bottom_bar_and_export_dialog() {
fn collect(shape: &egui::epaint::Shape, bounds: &mut Vec<(String, egui::Rect)>) {
match shape {
egui::epaint::Shape::Text(shape) => {
@@ -1323,7 +1429,7 @@ mod tests {
_ => {}
}
}
for language in [Locale::De, Locale::Uk, Locale::It] {
for language in [Locale::De, Locale::Uk, Locale::It, Locale::Fi] {
let ctx = fonts_ctx(&[]);
let services = crate::Services { pick_folder: Some(Box::new(|| None)), ..Default::default() };
let mut app = crate::LightcraftApp::new(lightcraft_engine::Session::with_demo(), services);
+2
View File
@@ -28,6 +28,7 @@ pub const LANGUAGE_COMMANDS: &[UiCommand] = &[
("app.language.czech", crate::i18n::Locale::Cs.name(), None, "Edit>Language"),
("app.language.italian", crate::i18n::Locale::It.name(), None, "Edit>Language"),
("app.language.georgian", crate::i18n::Locale::Ka.name(), None, "Edit>Language"),
("app.language.finnish", crate::i18n::Locale::Fi.name(), None, "Edit>Language"),
];
/// Every UI command: the languages, then everything else. `xtask parity` reads both tables from
@@ -54,6 +55,7 @@ pub fn language_from_command(id: &str) -> Option<crate::i18n::Locale> {
"app.language.czech" => Some(crate::i18n::Locale::Cs),
"app.language.italian" => Some(crate::i18n::Locale::It),
"app.language.georgian" => Some(crate::i18n::Locale::Ka),
"app.language.finnish" => Some(crate::i18n::Locale::Fi),
_ => 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"] {
for code in ["de", "es", "fr", "pt-br", "ru", "uk", "it", "ka", "ja", "zh-hans", "zh-hant", "pl", "cs", "fi"] {
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(), 14, "a language added: list its file above");
assert_eq!(Locale::ALL.len(), 15, "a language added: list its file above");
}
}
+62
View File
@@ -0,0 +1,62 @@
# Suomi
Suomen (`fi`, englanniksi **Finnish**, omakielinen nimi **Suomi**) saa käyttöön valitsemalla
**Muokkaa → Kieli → Suomi** tai **Asetukset → Yleiset → Kieli**. Muutos tulee voimaan heti ja
tallentuu seuraavaa käynnistystä varten. Komento on `app.language.finnish`; ohjauskanava
hyväksyy `ui.set {"language":"fi"}` sekä tunnisteet `fi-FI` ja `fi_FI.UTF-8`. Ikkunattomassa
ajossa voi käyttää `LIGHTCRAFT_LANGUAGE=fi`.
Luettelot `crates/ui-egui/locales/fi.json` ja `fi-formats.json` on laadittu tekoälyavusteisesti
(Claude) englanninkielisten lähdemerkkijonojen merkityksestä, kokonaisina fraaseina. Avainlista
on kaikkien muiden luetteloiden yhdiste. Suomenkielinen äidinkielinen puhuja (@roopeg) tarkistaa
käännöksen; ammattikääntäjä ei ole sitä käynyt läpi.
## Käytännöt ja laajuus
- Valikot: Tiedosto, Muokkaa, Näytä, Kuva, Ikkuna, Ohje.
- Library = Kirjasto, Develop = Kehitys, Collection = Kokoelma, Album = Albumi,
Preset = Esiasetus, Catalog = Katalogi (Lightroom-katalogi; keyword and model lists stay
”luettelo”), Snapshot = Tilannevedos, Version = Versio, Print (module) = Tulostus, the
command Print… = Tulosta….
- Exposure = Valotus, Contrast = Kontrasti, Clarity = Selkeys, Texture = Tekstuuri,
Dehaze = Sumunpoisto, Vibrance = Eloisuus, Saturation = Värikylläisyys, Hue = Sävy,
Tint = Värisävy, Temperature = Lämpötila, White Balance = Valkotasapaino.
- Highlights = Huippuvalot, Shadows = Varjot, Whites = Valkoiset, Blacks = Mustat.
- Sharpening = Terävöinti, Noise Reduction = Kohinanpoisto, Grain = Rakeisuus,
Vignette = Vinjetti, Color Grading = Väriluokittelu, Tone Curve = Sävykäyrä,
Defringe = Värivirheen korjaus, Feather = Häivytys.
- Crop = Rajaus, Straighten = Suorista, Mask = Maski, Brush = Sivellin,
Radial = Säteittäinen, Linear Gradient = Lineaarinen liukuväri,
Soft Proofing = Vedoksen esikatselu.
- Spot removal: Spot = Täplä, Heal = Paranna, Clone = Kloonaa. Point Color = Pistevärit and
Point Curve = Pistekäyrä keep ”piste”.
- Flag = Lippu, Pick = Valitse (picked photo = Valittu kuva), Reject = Hylätty, Rating = Luokitus (Star rating =
Tähtiluokitus), Label = Merkintä, Color Label = Värimerkintä, Keyword = Avainsana,
Smart Album = Älyalbumi, Filmstrip = Filminauha, Virtual Copy = Virtuaalikopio,
Stack = Pino, Template = Mallipohja (AI Model = Malli), Detection = Havaitseminen, Recently Deleted = Äskettäin poistetut, My Photos = Omat kuvat.
- Päivämäärä on muodossa `20.9.2026` (ilman etunollia), viikonpäivä alkaa pienellä kirjaimella
(`sunnuntai 20.9.2026`), kuukausi–vuosi on `9/2026` ja kellonaika 24-tuntinen `16:04:05`.
- Yksiköt: `kt`, `Mt`, `Gt`; prosentin edessä on väli (`50 %`).
- Määrät kirjoitetaan sanan jälkeen (”Tuotuja kuvia: 22”, ”Odottavia kuvia: 2”), koska
suomessa lukusanan jälkeinen substantiivi on yksikön partitiivissa (1 kuva, 2 kuvaa), eikä
englannin monikkopääte `s` voi valita muotoa. Englannin pääte kulutetaan `{:.0}`-merkinnällä.
Kun lause taipuu yksikössä kaikille luvuille (”{n} kuvalta”, ”{n} kuvassa”), luku voi olla
lauseen sisällä.
- Mukana toimitettu Inter-fontti sisältää ä, ö ja å sekä lainausmerkit ” ”. Lisäfonttia ei
tarvita.
Mitä uutta -ikkunan versiotiedotteet jäävät englanniksi, kuten useimmissa luetteloissa. Alemman
tason tekniset virheilmoitukset ja osa käännöskerroksen ulkopuolisista viesteistä jäävät
englanniksi. Käännös ei muuta kirjastotietoja eikä englanninkielisiä lähdemerkkijonoja.
## Testaus
`i18n::tests` tarkistaa komennon rekisteröinnin, vaihdon valikosta ja ohjauskanavasta,
kielen tallennuksen, luetteloiden kattavuuden, paikkamerkit, määrämuodot, päivämäärän, ajan ja
suomen kirjaimet. Rust tarkistaa muotoilumallit käännösvaiheessa.
Kielentarkistuksessa kannattaa katsoa erityisesti: ”Väriluokittelu” (Color Grading),
”Valoalueet / Valkoiset” (Highlights / Whites), ”Näyttövedos” (Soft Proofing),
”Älyalbumi”, ”Pino” (Stack), ”Pistevärit” (Point Color), ”Tekoälykohinanpoisto” sekä
luovien esiasetusten nimet. Kielen lisäämisen ohje on tiedostossa
[localization.md](localization.md).
+4 -2
View File
@@ -1,12 +1,12 @@
# Localization parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (de, es, fr, ja, uk catalogs filled; re-measured; Italian and Georgian interface catalogs added) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-17 · **Change:** minor (de, es, fr, ja, uk catalogs filled; re-measured; Italian, Georgian and Finnish interface catalogs added) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
How far each interface language is from `full`. How to add or maintain a language is in
[localization.md](localization.md); per-language notes are in `localization-<code>.md`
([zh-hans](localization-zh-hans.md), [zh-hant](localization-zh-hant.md), [ja](localization-ja.md),
[pt-br](localization-pt-br.md), [es](localization-es.md), [de](localization-de.md),
[ru](localization-ru.md), [ka](localization-ka.md)).
[ru](localization-ru.md), [fi](localization-fi.md), [ka](localization-ka.md)).
## The target
@@ -67,6 +67,7 @@ that is promising but is not recorded as review, so the column says `no (communi
| Ukrainian | `uk` | 2,383 / 2,398 (99%; 2026-10-12) | Cyrillic (Inter) | no (community edits) | partial (lower-layer errors English) | 2–4 h |
| Italian | `it` | 2,201 / 2,398 (92%, measured 2026-10-12; the rest is What's New) | Latin (Inter) | no (written with an agent; native-speaker pass pending) | partial (lower-layer errors English) | 3–5 h |
| Georgian | `ka` | 2,398 / 2,398 (100%: every plain and formats message any catalog has on 2026-10-12, plus the 27 display labels and 397 What's New lines no other catalog translates) | Geor: no craft-fonts face yet (boxes until one is available) | yes (native speaker) | partial (lower-layer errors English) | 2–4 h (+ a Georgian face) |
| Finnish | `fi` | 2,610 / 3,308 (79%, measured 2026-10-17: 2,313 plain + 297 formats; the rest is What's New) | Latin (Inter) | no (written with an agent; native-speaker review by the contributor pending) | partial (lower-layer errors English) | 3–5 h |
Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bokmål, Thai
(Thai needs shaping and line breaking without spaces). Each Latin-script one is ~10–16 h; Thai
@@ -93,6 +94,7 @@ Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bok
| Date | Change | Summary |
|---|---|---|
| 2026-10-17 | minor | Finnish interface catalog (`fi`): 2,610 of 3,308 messages (2,313 plain + 297 formats), What's New left in English |
| 2026-10-12 | minor | Italian interface catalog (`it`): 2,201 of 2,398 messages, What's New left in English |
| 2026-10-11 | minor | Shared work: the interface decides on error kinds, not English error text (#1046) |
| 2026-10-12 | minor | Georgian (`ka`) row: every catalog message |
+3 -2
View File
@@ -2,11 +2,11 @@
The interface ships in the language it is written in (English) plus every language in the table in
`crates/ui-egui/src/i18n.rs`. Today that is English, Simplified Chinese (`zh-hans`), Traditional
Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`), Russian (`ru`), French (`fr`), Ukrainian (`uk`), Czech (`cs`) and Georgian (`ka`). This file is the reference for **adding or maintaining a language**; the per-language notes
Chinese (`zh-hant`, Taiwan), Japanese (`ja`), Brazilian Portuguese (`pt-br`), Spanish (`es`), German (`de`), Russian (`ru`), French (`fr`), Ukrainian (`uk`), Czech (`cs`), Georgian (`ka`) and Finnish (`fi`). This file is the reference for **adding or maintaining a language**; the per-language notes
are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-hant.md`](localization-zh-hant.md),
[`localization-ja.md`](localization-ja.md), [`localization-pt-br.md`](localization-pt-br.md), [`localization-es.md`](localization-es.md),
[`localization-de.md`](localization-de.md), [`localization-ru.md`](localization-ru.md),
[`localization-cs.md`](localization-cs.md) and [`localization-ka.md`](localization-ka.md).
[`localization-cs.md`](localization-cs.md), [`localization-fi.md`](localization-fi.md) and [`localization-ka.md`](localization-ka.md).
## Adding a language
@@ -34,6 +34,7 @@ are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-h
Cs, "cs", "Čeština", "Latn", include_str!("../locales/cs.json");
It, "it", "Italiano", "Latn", include_str!("../locales/it.json");
Ka, "ka", "ქართული", "Geor", include_str!("../locales/ka.json");
Fi, "fi", "Suomi", "Latn", include_str!("../locales/fi.json");
}
```