Merge PR #1855: i18n: Swedish (sv) interface translation

Conflicts:
- README.md, docs/localization.md: language lists keep every language, Swedish appended
- assets/ATTRIBUTION.md: union of rows
- crates/ui-egui/src/i18n.rs: language table and command test list keep it, ka and sv; both tests kept
- crates/ui-egui/src/menus.rs: language commands keep it, ka and sv
- crates/ui-egui/src/panels/keyword_list.rs: code list gains sv; Locale::ALL.len() is 15
This commit is contained in:
aiongg
2026-10-22 02:37:05 +00:00
8 changed files with 2760 additions and 4 deletions
+1 -1
View File
@@ -451,7 +451,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, ქართული, Svenska) 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
+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/sv.json`, `crates/ui-egui/locales/sv-formats.json` | Swedish interface catalogs | LightCraft contributors | original work: clean-room Swedish translations of English source phrases | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`) | 2026-10-15 | none |
Notes
- Fonts authored or published by Adobe (Source Sans/Serif/Code, Source Han, …) are not used, even though some are
+293
View File
@@ -0,0 +1,293 @@
{
" ({unsure} with several look-alike files: use Locate)": " ({unsure} med flera likadana filer: använd Leta upp)",
" ({} in Recently Deleted)": " ({} i Nyligen raderade)",
" in {g} group{}": " i {g} grupp{}",
" · kept, not applied: {}": " · behålls men tillämpas inte: {}",
" · {:.0}% clipped": " · {:.0} % klippt",
" · {failed} couldn't be rendered": " · {failed} kunde inte renderas",
" · {n} skipped (could not be rendered)": " · {n} överhoppade (kunde inte renderas)",
" · {queued} waiting": " · {queued} väntar",
" · {skipped} skipped (file exists)": " · {skipped} överhoppade (filen finns)",
" · {} duplicate{} skipped": " · {} dubblett{} överhoppade",
" · {} failed: {}": " · {} misslyckades: {}",
" · {} moved": " · {} flyttade",
" · {} not readable": " · {} kunde inte läsas",
" · {} original{} left at the source": " · {} original{} kvar i källan",
" · {} restored from Recently Deleted": " · {} återställda från Nyligen raderade",
" — Capture One won't import a list with ; , < or > in a keyword: {names}": " — Capture One importerar inte en lista med ; , < eller > i ett nyckelord: {names}",
" — kept but not applied: {}": " — behålls men tillämpas inte: {}",
" — left out, as a keyword list can't hold them: {names}": " — utelämnade, eftersom en nyckelordslista inte kan innehålla dem: {names}",
" — not carried over: {}{}": " — fördes inte över: {}{}",
"+{n} more": "+{n} till",
"+{} more": "+{} till",
", {failed} file{} not readable": ", {failed} fil{} kunde inte läsas",
", {} label": ", {} etikett",
", {} star{}": ", {} stjärna{}",
"1 task is still running: {names}. Quitting stops it.": "1 uppgift körs fortfarande: {names}. Om du avslutar stoppas den.",
"; {kept} already had a location": "; {kept} hade redan en plats",
"; {n} gained synonyms": "; {n} fick synonymer",
"; {outside} outside its time range": "; {outside} utanför sitt tidsintervall",
"@{}: {} PRs, {} commits, +{} / −{} lines (Δ {}), +{} / −{} binary assets, {} – {}": "@{}: {} PR:er, {} commits, +{} / −{} rader (Δ {}), +{} / −{} binära resurser, {} – {}",
"Add {local_n} to My Photos": "Lägg till {local_n} i Mina foton",
"Add {} Photo{}": "Lägg till {} foto{}",
"Add {} Photo{}, restore {}": "Lägg till {} foto{}, återställ {}",
"Add “{}” to Favorites": "Lägg till ”{}” i Favoriter",
"Added {n} keyword{}": "Lade till {n} nyckelord{}",
"Added {n} photo{} to My Photos": "Lade till {n} foto{} i Mina foton",
"Added {n} photo{} to “{}”": "Lade till {n} foto{} i ”{}”",
"Added {what} to {name}": "Lade till {what} i {name}",
"Added “{keyword}” to {n} photo{}": "Lade till ”{keyword}” på {n} foto{}",
"Adding photos… {} of {}": "Lägger till foton… {} av {}",
"All {n} selected photos move by the same amount.": "Alla {n} markerade foton flyttas lika mycket.",
"Already in the library, in Recently Deleted: {} photo{} selected · right-click ▸ Restore, or Delete Permanently to import afresh": "Finns redan i biblioteket, i Nyligen raderade: {} foto{} markerade · högerklicka → Återställ, eller Radera permanent för att importera på nytt",
"Already in the library: {} photo{} selected in All Photos": "Finns redan i biblioteket: {} foto{} markerade i Alla foton",
"Applied {name}": "Tillämpade {name}",
"Apply to “{mask}” on Selected Photos": "Använd på ”{mask}” i markerade foton",
"Auto ({})": "Auto ({})",
"Auto Import: added {} photo{}": "Automatisk import: lade till {} foto{}",
"Auto Sync: edits apply to {n} photos": "Automatisk synkning: ändringar gäller {n} foton",
"Automatic ({} MB)": "Automatisk ({} MB)",
"Build 1:1 Previews ({scope})": "Bygg 1:1-förhandsgranskningar ({scope})",
"Build Smart Previews ({scope})": "Bygg smarta förhandsgranskningar ({scope})",
"Build Standard-Sized Previews ({scope})": "Bygg förhandsgranskningar i standardstorlek ({scope})",
"Building previews for {} photos…": "Bygger förhandsgranskningar för {} foton…",
"Click {}:1": "Klick {}:1",
"Couldn't open the editor: {e}": "Kunde inte öppna redigeraren: {e}",
"Create Keyword Tag Inside “{name}”…": "Skapa nyckelordstagg i ”{name}”…",
"Create the keyword “{name}”": "Skapa nyckelordet ”{name}”",
"Create {n} Virtual Copies": "Skapa {n} virtuella kopior",
"Creates {} stacks from {} of {scope}": "Skapar {} staplar av {} i {scope}",
"Decoder: {reason}": "Avkodare: {reason}",
"Delete \"{label}\"": "Radera ”{label}”",
"Delete \"{label}\" (and the mask)": "Radera ”{label}” (och masken)",
"Delete the effect preset “{name}”?": "Radera effektförinställningen ”{name}”?",
"Delete the keyword “{name}”?": "Radera nyckelordet ”{name}”?",
"Delete these {n} effect presets?": "Radera dessa {n} effektförinställningar?",
"Delete {n} effect preset{}? This can't be undone.": "Radera {n} effektförinställning{}? Det går inte att ångra.",
"Delete {n} Photos": "Radera {n} foton",
"Delete “{current}”": "Radera ”{current}”",
"Delete “{name}”?": "Radera ”{name}”?",
"Denoising {name}: {done} of {total} tiles{}": "Brusreducerar {name}: {done} av {total} rutor{}",
"Discard Smart Previews ({scope})": "Släng smarta förhandsgranskningar ({scope})",
"Done ({what}): {done} photo{plural}": "Klart ({what}): {done} foto{plural}",
"Downloads from {host}. Once it has arrived and checked out, LightCraft installs it, {after}.": "Laddas ned från {host}. När den har kommit fram och kontrollerats installerar LightCraft den, {after}.",
"Duplicate \"{label}\"": "Duplicera ”{label}”",
"Effect: {name}": "Effekt: {name}",
"Example: {example}": "Exempel: {example}",
"Exported to {path}": "Exporterade till {path}",
"Exported {n} keyword{}": "Exporterade {n} nyckelord{}",
"Exported {n} photo{}": "Exporterade {n} foto{}",
"Exported {ok} of {total} photo{}": "Exporterade {ok} av {total} foto{}",
"Exported {} effect preset{}": "Exporterade {} effektförinställning{}",
"Exported {} point curve preset{}": "Exporterade {} punktkurvförinställning{}",
"Exported {} preset{}": "Exporterade {} förinställning{}",
"Exporting {} of {total}": "Exporterar {} av {total}",
"Filter bar — {active} active filter{}": "Filterfält — {active} aktiva filter{}",
"Filtered: {label}": "Filtrerat: {label}",
"Focus {:.0}": "Fokus {:.0}",
"Folder “{}” (and its subfolders)": "Mappen ”{}” (och dess undermappar)",
"Found {n} missing photo{}; {left} still missing{unsure}": "Hittade {n} saknade foto{}; {left} saknas fortfarande{unsure}",
"Healed {n} dust spot{}": "Retuscherade {n} dammfläck{}",
"Import cancelled · {} photo{} added": "Importen avbröts · {} foto{} tillagda",
"Import failed: {e}": "Importen misslyckades: {e}",
"Import warnings ({n}):\n{warnings}": "Importvarningar ({n}):\n{warnings}",
"Imported {e} mask effect{} (Masking ▸ Effect)": "Importerade {e} maskeffekt{} (Maskering → Effekt)",
"Imported {name}": "Importerade {name}",
"Imported {n} point curve preset{}": "Importerade {n} punktkurvförinställning{}",
"Imported {n} preset{}": "Importerade {n} förinställning{}",
"Imported {n} preset{}, {f} file{} not readable": "Importerade {n} förinställning{}, {f} fil{} kunde inte läsas",
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "Importerade {profiles} profil{} (Profilbläddrare → deras grupper)",
"Imported {} photo{}": "Importerade {} foto{}",
"In line for AI Denoise ({ahead} ahead)…": "I kö för AI-brusreducering ({ahead} före)…",
"Insert at the cursor (also written {})": "Infoga vid textmarkören (skrivs även {})",
"It is taken off {count} photo{}, with the keywords below it. Undo brings it back.": "Det tas bort från {count} foto{}, tillsammans med nyckelorden under det. Ångra återställer det.",
"Keywords of {n} selected photos": "Nyckelord för {n} markerade foton",
"Label Set: {name}": "Etikettuppsättning: {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 kan inte avkoda den här raw-varianten än ({}). Du redigerar kamerans inbäddade JPEG-förhandsgranskning, som redan innehåller kamerans bildstil (t.ex. Monokrom) och vitbalans.",
"LightCraft could not open its window: {e}": "LightCraft kunde inte öppna sitt fönster: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft kunde inte öppna sitt fönster: {e}\n\nDetaljer finns i loggfilen: {path}",
"LightCraft stopped unexpectedly: {e}": "LightCraft avslutades oväntat: {e}",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft avslutades oväntat: {e}\n\nDetaljer finns i loggfilen: {path}",
"Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Lightroom-katalogen har granskats: {photos} foton, {collections} samlingar, {missing} saknas",
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "Lightroom-geometrin för {path} bevarades inte; sparad transformering med guidad Upright stöds inte.",
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Lightroom-geometrin för {path} bevarades inte; den sparade transformeringen ignoreras eftersom Upright är av.",
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Lightroom-geometrin för {path} bevarades inte; sparad Upright-transformering stöds inte.",
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Lightroom-geometrin bevarades inte för {path}; {mode} beräknas om.",
"Lightroom import complete: {imported} photos added": "Lightroom-importen är klar: {imported} foton har lagts till",
"Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom-importen är klar: {imported} foton har lagts till, {failed} misslyckades",
"Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom-importen är klar: {imported} foton har lagts till; indexvarning: {warning}",
"Lightroom import failed: {e}{warning}": "Lightroom-importen misslyckades: {e}{warning}",
"Lightroom inspection failed: {e}{warning}": "Granskningen av Lightroom misslyckades: {e}{warning}",
"Making the denoised picture… {}%": "Skapar den brusreducerade bilden… {}%",
"Matches: {}": "Matchar: {}",
"Merging… {stage} {:.0}%": "Sammanfogar… {stage} {:.0}%",
"MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. Typsnitt: {} (OFL). Ikoner: original.",
"Move {what} to Recently Deleted?": "Vill du flytta {what} till Nyligen raderade?",
"Moved {} photo(s)": "Flyttade {} foto(n)",
"Moved; {} photo(s) relinked": "Flyttat; {} foto(n) har länkats om",
"Named ({named_n})": "Namngivna ({named_n})",
"New Folder in “{name}”": "Ny mapp i ”{name}”",
"new, inside {parent}": "ny, inuti {parent}",
"none (CPU rendering): {why}": "ingen (CPU-rendering): {why}",
"not added to Apple Photos: {e}": "inte tillagt i Apple Photos: {e}",
"On {have} of {of} selected photos": "På {have} av {of} markerade foton",
"Painting “{k}”: click photos to add or remove it · Esc stops": "Målar ”{k}”: klicka på foton för att lägga till eller ta bort det · Esc avslutar",
"Paste into {n} photo{} — only settings that were copied are pasted": "Klistra in i {n} foto{} – endast inställningar som kopierades klistras in",
"Preset: {name}": "Förinställning: {name}",
"Preview only — editing the camera's embedded JPEG ({})": "Endast förhandsgranskning – du redigerar kamerans inbäddade JPEG ({})",
"Preview only — {}": "Endast förhandsgranskning – {}",
"Previews ready for {done} photo{plural}": "Förhandsgranskningar klara för {done} foto{plural}",
"Profile: {}": "Profil: {}",
"Proof Copy ({})": "Korrekturkopia ({})",
"Proof Preview · {}": "Korrekturförhandsgranskning · {}",
"Put inside “{parent}”": "Placera i ”{parent}”",
"Quick Develop · {n} photos": "Snabbframkalla · {n} foton",
"Quick Develop: {name}": "Snabbframkalla: {name}",
"Rated {}": "Betygsatt {}",
"Reading Lightroom catalog… {done} of {total}": "Läser Lightroom-katalogen… {done} av {total}",
"Reading photos… {done} of {total}": "Läser foton… {done} av {total}",
"Reading photos… {} of {}": "Läser foton… {} av {}",
"Redo {}": "Gör om {}",
"Remove “{name}”": "Ta bort ”{name}”",
"Remove “{name}” and its {count} photo{} from the library?": "Vill du ta bort ”{name}” och dess {count} foto{} från biblioteket?",
"Remove “{}” from Favorites": "Ta bort ”{}” från Favoriter",
"Removed from {}": "Borttaget från {}",
"Removed {what} from {name}": "Tog bort {what} från {name}",
"Removed “{name}” from {n} photos. Edit ▸ Undo puts it back.": "Tog bort ”{name}” från {n} foton. Redigera → Ångra lägger tillbaka det.",
"Rename {n} Photos…": "Byt namn på {n} foton…",
"Rename “{name}”": "Byt namn på ”{name}”",
"Renames {n} face{} across the library. The files are not changed.": "Byter namn på {n} ansikte{} i hela biblioteket. Filerna ändras inte.",
"Renames “{from}” on {n} photo{} (keywords below it too). Use | for levels, e.g. Travel|Italy. An existing name merges the two.": "Byter namn på ”{from}” på {n} foto{} (även nyckelord under det). Använd | för nivåer, t.ex. Resor|Italien. Ett befintligt namn sammanfogar de två.",
"Rendering on the CPU: {why}": "Renderar på processorn: {why}",
"Replace {} with:": "Ersätt {} med:",
"Reset {name}": "Återställ {name}",
"Reset {} Channel": "Återställ kanalen {}",
"Restore {}": "Återställ {}",
"Restore {} Photo{}": "Återställ {} foto{}",
"Restored {} photo{} from Recently Deleted": "Återställde {} foto{} från Nyligen raderade",
"Running on the processor: {}.": "Körs på processorn: {}.",
"Running on {}.": "Körs på {}.",
"Saved export preset “{}”": "Exportförinställningen ”{}” har sparats",
"Saved template {name}": "Mallen {name} har sparats",
"Scores {scope} for focus and exposure and finds similar shots taken within seconds of each other (bursts).": "Bedömer {scope} efter fokus och exponering och hittar likartade bilder tagna inom några sekunder från varandra (serier).",
"Show {hidden} hidden location{}": "Visa {hidden} dold plats{}",
"Showing {n} of {}": "Visar {n} av {}",
"Shows the model's terms, then downloads {} from {host}, installs it and starts using it. Nothing else is sent.": "Visar modellens villkor, hämtar sedan {} från {host}, installerar den och börjar använda den. Inget annat skickas.",
"Skipped {problem}": "Hoppade över {problem}",
"Slideshow exported: {what}": "Bildspelet har exporterats: {what}",
"Smart album: {}": "Smart album: {}",
"Tagged {n} photo{} from the tracklog": "Taggade {n} foto{} från spårloggen",
"Template preview: {name}": "Förhandsgranskning av mall: {name}",
"The SAM 3 download failed: {e}": "Hämtningen av SAM 3 misslyckades: {e}",
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "XMP-sidofilerna för {n} foton kunde inte skrivas (är mappen offline?). LightCraft fortsätter att försöka.",
"the {n} selected photos": "de {n} markerade fotona",
"the {} photos in view": "de {} fotona i vyn",
"There is a keyword set “{name}” already": "Det finns redan en nyckelordsuppsättning ”{name}”",
"There is a “{name}” at the top level already. Merge the two?": "Det finns redan ett ”{name}” på översta nivån. Vill du sammanfoga dem?",
"these {count} photos": "dessa {count} foton",
"Trained on: {provenance}": "Tränad på: {provenance}",
"Undo {}": "Ångra {}",
"Unknown tag{s} {} stay{} as typed (see Tags)": "Okända taggar ({}) behåll{s}{} som de skrevs (se Taggar)",
"Update “{name}” with Current Settings": "Uppdatera ”{name}” med aktuella inställningar",
"Updated {name}": "{name} har uppdaterats",
"Updated “{name}”": "”{name}” har uppdaterats",
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "Version {} – ett fotobibliotek och raw-framkallare i ren Rust, utvecklat enligt clean room-metoden.",
"Version: {}": "Version: {}",
"Virtual copy “{name}” of {of}": "Virtuell kopia ”{name}” av {of}",
"Working on {what} for {} photos…": "Arbetar med {what} för {} foton…",
"XMP adjustments were not imported for {path}: {fields}.": "XMP-justeringar importerades inte för {path}: {fields}.",
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "XMP-profilen \"{profile}\" tillämpades inte för {path}; färgerna kan skilja sig från Lightroom.",
"{:.1} MB": "{:.1} MB",
"{count} more warnings.": "{count} varningar till.",
"{count} smart preview{} · {:.1} MB": "{count} smarta förhandsgranskning{} · {:.1} MB",
"{done} of {total}": "{done} av {total}",
"{done} of {total} MB": "{done} av {total} MB",
"{file} · {size}": "{file} · {size}",
"{f} folders (and their subfolders)": "{f} mappar (och deras undermappar)",
"{label} Grading": "Gradering: {label}",
"{label} {d:+} on every selected photo": "{label} {d:+} på varje markerat foto",
"{label} {index} of {n}": "{label} {index} av {n}",
"{label}, {n} photos": "{label}, {n} foton",
"{matching} of {t} photos": "{matching} av {t} foton",
"{matching} photos": "{matching} foton",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} original saknas. Deras katalogposter finns kvar; använd Sök efter saknade foton för att länka om dem.",
"{mode} spot {} of {}": "{mode} – punkt {} av {}",
"{month}": "{month}",
"{month} {year}": "{month} {year}",
"{msg} ({failed} failed)": "{msg} ({failed} misslyckades)",
"{name} Label": "Etiketten {name}",
"{name} Off": "{name} av",
"{name} On": "{name} på",
"{name} opened for editing; it is stacked with the original": "{name} öppnades för redigering; den är staplad med originalet",
"{name} opened in the other editor": "{name} öppnades i den andra redigeraren",
"{name} · {terms}": "{name} · {terms}",
"{name} — double-click to reset it": "{name} – dubbelklicka för att återställa",
"{n} added to Apple Photos": "{n} tillagda i Apple Photos",
"{n} added to Apple Photos album “{album}”": "{n} har lagts till i Apple Photos-albumet ”{album}”",
"{n} change{} couldn't be written to disk: {e}\nIf you quit now, {} lost.": "{n} ändring{} kunde inte skrivas till disken: {e}\nOm du avslutar nu går {} förlorade.",
"{n} change{} saved in memory but not written to disk: {e}\nLightCraft retries automatically; quitting now would lose {}.": "{n} ändring{} har sparats i minnet men inte skrivits till disken: {e}\nLightCraft försöker igen automatiskt. Om du avslutar nu går {} förlorade.",
"{n} change{} saved in memory but not written to disk: {e} — LightCraft will retry": "{n} ändring{} har sparats i minnet men inte skrivits till disken: {e} – LightCraft försöker igen",
"{n} found · {sel} selected": "{n} hittade · {sel} markerade",
"{n} JPEG files": "{n} JPEG-filer",
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "{n} lokala foto{} hoppades över: album innehåller foton från biblioteket (lägg först till i Mina foton)",
"{n} originals opened in the other editor": "{n} original har öppnats i den andra redigeraren",
"{n} pages": "{n} sidor",
"{n} photos": "{n} foton",
"{n} photos in view · {slides} slides · {time}": "{n} foton i vyn · {slides} bilder · {time}",
"{n} photos in {} are hidden by the filters above": "{n} foton i {} döljs av filtren ovan",
"{n} photos opened for editing; each is stacked with its original": "{n} foton har öppnats för redigering; vart och ett staplas med sitt original",
"{n} photo{}": "{n} foto{}",
"{n} photo{} match · updates automatically as photos change": "{n} foto{} matchar · uppdateras automatiskt när fotona ändras",
"{n} photo{} now · updates automatically as photos change": "{n} foto{} just nu · uppdateras automatiskt när fotona ändras",
"{n} preset{}": "{n} förinställning{}",
"{n} raws can now be developed from their raw data": "{n} raw-filer kan nu framkallas från sina rådata",
"{n} similar photo{} · View ▸ Clear Filters to see all": "{n} likartade foto{} · Visa → Rensa filter för att se alla",
"{n} tasks are still running: {names}. Quitting stops them.": "{n} uppgifter körs fortfarande: {names}. Om du avslutar stoppas de.",
"{n} {noun}": "{n} {noun}",
"{pct} %": "{pct} %",
"{photos} catalog records processed; {collections} collections.": "{photos} katalogposter bearbetade; {collections} samlingar.",
"{pid} (missing)": "{pid} (saknas)",
"{sel_n} selected · {counted}": "{sel_n} markerade · {counted}",
"{size} GB": "{size} GB",
"{stage}… {:.0}%": "{stage}… {:.0} %",
"{s} s of AV1 video": "{s} s AV1-video",
"{verb} {n} Photo{}": "{verb} {n} foto{}",
"{weekday}, {day}": "{weekday} {day}",
"{weekday}, {day} {month} {year}": "{weekday} {day} {month} {year}",
"{what} merge added": "{what}-sammanfogning tillagd",
"{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "{what}-sammanfogning tillagd med {mp}, den största storlek ett panorama kan ha (full storlek är {full})",
"{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has": "{what}-sammanfogning tillagd med {mp}: full storlek, {full}, kräver mer minne än den här datorn har",
"{what} merge cancelled": "{what}-sammanfogning avbruten",
"{what} merge failed: {e}": "{what}-sammanfogning misslyckades: {e}",
"{year}": "{year}",
"{{date:…}} directives: {}": "{{date:…}}-direktiv: {}",
"{} and higher": "{} och högre",
"{} contributors · {} commits": "{} bidragsgivare · {} commits",
"{} failed": "{} misslyckades",
"{} files": "{} filer",
"{} files and {f} folder{}": "{} filer och {f} mapp{}",
"{} item{}": "{} objekt{}",
"{} KB": "{} kB",
"{} label (click more labels to show any of them)": "Etiketten {} (klicka på fler etiketter för att visa foton med någon av dem)",
"{} MB": "{} MB",
"{} of 4 guides — drag along lines that should be vertical or horizontal.": "{} av 4 stödlinjer – dra längs linjer som ska vara vertikala eller horisontella.",
"{} of {} from {host}": "{} av {} från {host}",
"{} photos": "{} foton",
"{} photos · exposures {} EV": "{} foton · exponeringar {} EV",
"{} photos · {} · {:.0}° × {:.0}°": "{} foton · {} · {:.0}° × {:.0}°",
"{} photo{} not readable": "{} foto{} kunde inte läsas",
"{} sample{}": "{} sample{}",
"{} sec": "{} s",
"{} spot(s) on this photo": "{} fläck(ar) på det här fotot",
"{} · {}. Kept in the library's “denoise” folder; the oldest ones that no photo uses go first when the limit is reached.": "{} · {}. Sparas i bibliotekets mapp ”denoise”; de äldsta som inget foto använder tas bort först när gränsen nås.",
"{} — show their photos": "{} – visa deras foton",
"· {dups} already in the library": "· {dups} finns redan i biblioteket",
"· {in_source} identical copies": "· {in_source} identiska kopior",
"· {n} photo{}": "· {n} foto{}",
"· {trashed} in Recently Deleted": "· {trashed} i Nyligen raderade",
"“{parent}” has a “{name}” already. Merge the two?": "”{parent}” har redan en ”{name}”. Vill du sammanfoga dem?",
"“{to}” is already a person. Combining them makes one person; Undo separates them again.": "”{to}” är redan en person. Om du kombinerar dem blir de en person; Ångra skiljer dem åt igen.",
"• {assumption}": "• {assumption}",
"… and {more} more": "… och {more} till",
"…and {more} more": "…och {more} till"
}
File diff suppressed because it is too large Load Diff
+42
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");
Sv, "sv", "Svenska", "Latn", include_str!("../locales/sv.json");
}
// The settings file stores the BCP-47 code (`"zh-hans"`), never the Rust variant name, so a
@@ -565,6 +566,46 @@ mod tests {
assert!(capitalizes('a') && capitalizes('ж') && !capitalizes('ქ') && !capitalizes('Ⴀ') && !capitalizes('ⴀ'));
}
#[test]
fn swedish_switches_persists_and_formats_without_changing_user_values() {
use crate::control::{ControlRequest, Outcome};
let mut app = crate::LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default());
app.run("app.language.swedish", serde_json::json!({})).unwrap();
assert_eq!(app.ui.language, Locale::Sv);
assert_eq!(Locale::Sv.name(), "Svenska");
assert_eq!(Locale::Sv.script(), "Latn");
assert_eq!(crate::menubar::checked(&app, "app.language.swedish"), Some(true));
assert_eq!(tr("File"), "Fil");
assert_eq!(tr("Develop"), "Framkalla");
assert_eq!(tr("Exposure"), "Exponering");
assert_eq!(date_group_label("2026", false), "2026");
assert!(date_group_label("2026-09-20", false).starts_with("Söndag 20 "), "{}", date_group_label("2026-09-20", false));
assert_eq!(builtin_label("Warm Glow", false), "Warm Glow");
assert_eq!(tr("photo-{title}.jpg"), "photo-{title}.jpg");
let saved = serde_json::to_string(&app.ui).unwrap();
assert!(saved.contains("\"sv\""), "{saved}");
assert_eq!(serde_json::from_str::<crate::state::UiState>(&saved).unwrap().language, Locale::Sv);
let ctx = egui::Context::default();
for tag in ["sv", "sv-SE", "sv_SE.UTF-8", "sv-FI"] {
assert_eq!(Locale::parse_tag(tag), Some(Locale::Sv), "{tag}");
let (request, _) = ControlRequest::new("ui.set", serde_json::json!({"language": tag}));
let Outcome::Done(reply) = crate::control::handle(&mut app, &ctx, &request) else { panic!("expected reply") };
assert_eq!(reply["ok"], true);
assert_eq!(language(), Locale::Sv);
}
let text = painted_text(&ctx, &mut app, Locale::Sv);
assert!(text.contains("Fil") && text.contains("Exponering"), "{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() {
assert!(fonts.has_glyph(&font, ch), "missing Swedish glyph {ch}");
}
}
});
set_language(Locale::En);
}
#[test]
fn every_language_has_a_catalog_covering_the_source() {
assert!(crate::i18n::Locale::En.catalog().is_empty(), "the source language needs no catalog");
@@ -826,6 +867,7 @@ mod tests {
("app.language.czech", Locale::Cs),
("app.language.italian", Locale::It),
("app.language.georgian", Locale::Ka),
("app.language.swedish", Locale::Sv),
];
// One command per language, and every command reachable from the menu table.
assert_eq!(commands.len(), Locale::ALL.len());
+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.swedish", crate::i18n::Locale::Sv.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.swedish" => Some(crate::i18n::Locale::Sv),
_ => 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", "sv"] {
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");
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
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 Swedish (`sv`). 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),