mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-24 10:09:01 -06:00
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:
co-authored by
Claude Sonnet 5.5
parent
a2a41383c4
commit
494d9d77bb
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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).
|
||||
@@ -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 |
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user