Merge PR #1823 (rebased)

Georgian next to Italian in the language table, menus, catalogs list and docs; ka-formats.json lists the format
strings other PRs added, in English until translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Brandon ThomasandClaude Opus 5.5 committed 2026-10-15 02:39:01 -04:00
commit 51d8018e27
16 files changed
+3175 -20

No files matched your search

+1 -1
View File
@@ -426,7 +426,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, Українська, Čeština) or **Settings → General →
**Interface language:** **Edit → Language** (English, 简体中文, 繁體中文(台灣), 日本語, Português (Brasil), Español, Deutsch, Русский, Français, Українська, Čeština, ქართული) 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%).
Also shipped: Traditional Chinese (87%), Russian (74%), Ukrainian (99%), Italian (92%), Georgian (100%).
## Upcoming
@@ -191,6 +191,7 @@ Ranked toward beta; detail in [docs/roadmap.md](docs/roadmap.md), gaps in [docs/
| 2026-10-14 | minor | 17 uncompressed Olympus and Samsung models verified on one CC0 file each (254 models verified, 18%); headline numbers unchanged |
| 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-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 |
| 2026-10-12 | minor | 22 more CR3 bodies verified, EOS R6 Mark III white balance: RAW decoding 77% → 83%, RAW ~56% → ~60%; mainstream 49% → 51%, essentials 64% → 65%; full unchanged at ~61% |
+1
View File
@@ -33,6 +33,7 @@ covers a whole directory), or if a licence file referenced in the Licence column
| `crates/ui-egui/locales/uk.json`, `crates/ui-egui/locales/uk-formats.json` | Ukrainian interface catalogs | @dmatviichuk with Codex; PhotoCraft, FilmCraft and VectorCraft translation contributors for reused labels | original translations from English source; 337 initial English-key matches reviewed from [PhotoCraft 69c41f3](https://github.com/storytold/photocraft/blob/69c41f3523e147eb215da8c7b4c29b18b35d1c63/crates/ui-egui/src/i18n/uk.tsv), [FilmCraft 4854c27](https://github.com/storytold/filmcraft/blob/4854c277536fe8118257362304b6a7d82d8e704b/crates/ui-egui/src/i18n/uk.tsv), [VectorCraft 4b44265](https://github.com/storytold/vectorcraft/blob/4b44265965fdd6ffedfd6182a892c1a6c360d344/crates/ui-egui/src/i18n/uk.tsv) | MIT OR Apache-2.0 (`LICENSE-MIT`, `LICENSE-APACHE`, also the source repositories' licences) | 2026-10-09 | adapted to LightCraft photo-development context, completed catalogs, reviewed spelling and placeholders |
| `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 |
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} რამდენიმე მსგავსი ფაილით: გამოიყენეთ „მოძებნა“)",
" ({} in Recently Deleted)": " ({} ახლახან წაშლილებში)",
" · {:.0}% clipped": " · {:.0}% მოკვეთილია",
" · {failed} couldn't be rendered": " · ვერ დარენდერდა: {failed}",
" · {queued} waiting": " · რიგშია: {queued}",
" · {skipped} skipped (file exists)": " · გამოტოვებულია: {skipped} (ფაილი უკვე არსებობს)",
" · {} duplicate{} skipped": " · გამოტოვებული დუბლიკატი: {}{:.0}",
" · {} failed: {}": " · ვერ მოხერხდა: {}: {}",
" · {} moved": " · გადატანილია: {}",
" · {} not readable": " · ვერ იკითხება: {}",
" · {} original{} left at the source": " · წყაროში დატოვებული ორიგინალი: {}{:.0}",
" · {} restored from Recently Deleted": " · ახლახან წაშლილებიდან აღდგენილია: {}",
" — Capture One won't import a list with ; , < or > in a keyword: {names}": " — Capture One ვერ შემოიტანს სიას, სადაც საკვანძო სიტყვა შეიცავს ; , < ან > სიმბოლოს: {names}",
" — left out, as a keyword list can't hold them: {names}": " — გამოტოვებულია, რადგან საკვანძო სიტყვების სია მათ ვერ დაიტევს: {names}",
" — not carried over: {}{}": " — არ გადავიდა: {}{}",
"+{n} more": "+ კიდევ {n}",
"+{} more": "+ კიდევ {}",
", {failed} file{} not readable": ", ვერ იკითხება ფაილი: {failed}{:.0}",
", {} label": ", იარლიყი: {}",
", {} star{}": ", ვარსკვლავი: {}{:.0}",
"1 task is still running: {names}. Quitting stops it.": "ჯერ კიდევ მიმდინარეობს 1 ამოცანა: {names}. გასვლა მას შეწყვეტს.",
"; {kept} already had a location": "; მდებარეობა უკვე ჰქონდა: {kept}",
"; {n} gained synonyms": "; სინონიმები დაემატა: {n}",
"; {outside} outside its time range": "; მარშრუტის დროის დიაპაზონს გარეთაა: {outside}",
"@{}: {} PRs, {} commits, +{} / −{} lines (Δ {}), +{} / −{} binary assets, {} – {}": "@{}: PR: {}, კომიტი: {}, სტრიქონი: +{} / −{} (Δ {}), ბინარული ფაილი: +{} / −{}, {} – {}",
"Add {local_n} to My Photos": "ჩემს ფოტოებში დამატება: {local_n}",
"Add {} Photo{}": "ფოტოს დამატება: {}{:.0}",
"Add {} Photo{}, restore {}": "ფოტოს დამატება: {}{:.0}, აღდგენა: {}",
"Add “{}” to Favorites": "„{}“ რჩეულებში დამატება",
"Added {n} keyword{}": "დაემატა საკვანძო სიტყვა: {n}{:.0}",
"Added {n} photo{} to My Photos": "ჩემს ფოტოებს დაემატა ფოტო: {n}{:.0}",
"Added {n} photo{} to “{}”": "„{1}“-ს დაემატა ფოტო: {n}{0:.0}",
"Added {what} to {name}": "{what}: დაემატა {name}-ს",
"Added “{keyword}” to {n} photo{}": "„{keyword}“ დაემატა ფოტოს: {n}{:.0}",
"Adding photos… {} of {}": "ფოტოების დამატება… {} / {}",
"All {n} selected photos move by the same amount.": "მონიშნული ფოტოები ({n}) ერთი და იმავე სიდიდით გადაინაცვლებს.",
"Already in the library, in Recently Deleted: {} photo{} selected · right-click ▸ Restore, or Delete Permanently to import afresh": "უკვე ბიბლიოთეკაშია, ახლახან წაშლილებში: მონიშნულია ფოტო: {}{:.0} · მარჯვენა დაწკაპუნება → აღდგენა, ან სამუდამოდ წაშლა თავიდან იმპორტისთვის",
"Already in the library: {} photo{} selected in All Photos": "უკვე ბიბლიოთეკაშია: „ყველა ფოტოში“ მონიშნულია ფოტო: {}{:.0}",
"Auto ({})": "ავტომატური ({})",
"Auto Import: added {} photo{}": "ავტოიმპორტი: დაემატა ფოტო: {}{:.0}",
"Auto Sync: edits apply to {n} photos": "ავტოსინქრონიზაცია: ცვლილებები ვრცელდება ფოტოებზე: {n}",
"Automatic ({} MB)": "ავტომატური ({} MB)",
"Build 1:1 Previews ({scope})": "1:1 წინასწარი ხედების შექმნა ({scope})",
"Build Smart Previews ({scope})": "ჭკვიანი წინასწარი ხედების შექმნა ({scope})",
"Build Standard-Sized Previews ({scope})": "სტანდარტული ზომის წინასწარი ხედების შექმნა ({scope})",
"Building previews for {} photos…": "იქმნება წინასწარი ხედები ფოტოებისთვის: {}…",
"Click {}:1": "დაწკაპუნება {}:1",
"Couldn't open the editor: {e}": "რედაქტორი ვერ გაიხსნა: {e}",
"Create Keyword Tag Inside “{name}”…": "საკვანძო სიტყვის შექმნა „{name}“-ში…",
"Create {n} Virtual Copies": "ვირტუალური ასლების შექმნა: {n}",
"Creates {} stacks from {} of {scope}": "შეიქმნება დასტა: {} — {}-დან ({scope})",
"Decoder: {reason}": "დეკოდერი: {reason}",
"Delete \"{label}\"": "„{label}“-ის წაშლა",
"Delete \"{label}\" (and the mask)": "„{label}“-ის წაშლა (ნიღაბთან ერთად)",
"Delete the keyword “{name}”?": "წაიშალოს საკვანძო სიტყვა „{name}“?",
"Delete {n} Photos": "ფოტოების წაშლა: {n}",
"Delete “{current}”": "„{current}“-ის წაშლა",
"Denoising {name}: {done} of {total} tiles{}": "{name}-ის ხმაურის მოცილება: ფრაგმენტი {done} / {total}{}",
"Discard Smart Previews ({scope})": "ჭკვიანი წინასწარი ხედების მოცილება ({scope})",
"Done ({what}): {done} photo{plural}": "მზადაა ({what}): ფოტო: {done}{plural:.0}",
"Downloads from {host}. Once it has arrived and checked out, LightCraft installs it, {after}.": "ჩამოიტვირთება {host}-დან. მიღებისა და შემოწმების შემდეგ LightCraft მას დააყენებს, {after}.",
"Duplicate \"{label}\"": "„{label}“-ის დუბლირება",
"Example: {example}": "მაგალითი: {example}",
"Exported {n} keyword{}": "ექსპორტირებულია საკვანძო სიტყვა: {n}{:.0}",
"Exported {n} photo{}": "ექსპორტირებულია ფოტო: {n}{:.0}",
"Exported {ok} of {total} photo{}": "ექსპორტირებულია ფოტო: {ok} / {total}{:.0}",
"Exported {} point curve preset{}": "ექსპორტირებულია წერტილოვანი მრუდის პრესეტი: {}{:.0}",
"Exported {} preset{}": "ექსპორტირებულია პრესეტი: {}{:.0}",
"Exporting {} of {total}": "ექსპორტი: {} / {total}",
"Filter bar — {active} active filter{}": "ფილტრების ზოლი — აქტიური ფილტრი: {active}{:.0}",
"Filtered: {label}": "გაფილტრულია: {label}",
"Focus {:.0}": "ფოკუსი {:.0}",
"Folder “{}” (and its subfolders)": "საქაღალდე „{}“ (ქვესაქაღალდეებით)",
"Found {n} missing photo{}; {left} still missing{unsure}": "ნაპოვნია დაკარგული ფოტო: {n}{:.0}; კვლავ აკლია: {left}{unsure}",
"Healed {n} dust spot{}": "აღდგენილია მტვრის ლაქა: {n}{:.0}",
"Import cancelled · {} photo{} added": "იმპორტი გაუქმდა · დაემატა ფოტო: {}{:.0}",
"Import failed: {e}": "იმპორტი ვერ მოხერხდა: {e}",
"Import warnings ({n}):\n{warnings}": "იმპორტის გაფრთხილებები ({n}):\n{warnings}",
"Imported {n} point curve preset{}": "იმპორტირებულია წერტილოვანი მრუდის პრესეტი: {n}{:.0}",
"Imported {n} preset{}": "იმპორტირებულია პრესეტი: {n}{:.0}",
"Imported {n} preset{}, {f} file{} not readable": "იმპორტირებულია პრესეტი: {n}{:.0}, ვერ იკითხება ფაილი: {f}{:.0}",
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "იმპორტირებულია პროფილი: {profiles}{:.0} (პროფილების ბრაუზერი → მათი ჯგუფები)",
"Imported {} photo{}": "იმპორტირებულია ფოტო: {}{:.0}",
"In line for AI Denoise ({ahead} ahead)…": "AI ხმაურის მოცილების რიგში (წინ არის: {ahead})…",
"Insert at the cursor (also written {})": "კურსორის ადგილას ჩასმა (ასევე იწერება როგორც {})",
"It is taken off {count} photo{}, with the keywords below it. Undo brings it back.": "მოიხსნება ფოტოებიდან ({count}){:.0} მის ქვემოთ მყოფ საკვანძო სიტყვებთან ერთად. დაბრუნება მას აღადგენს.",
"Label Set: {name}": "იარლიყების ნაკრები: {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 ჯერ ვერ დეკოდირებს RAW-ის ამ ვარიანტს ({}). თქვენ არედაქტირებთ კამერის ჩაშენებულ JPEG-ხედს, რომელიც უკვე შეიცავს კამერის გამოსახულების სტილს (მაგ., მონოქრომულს) და თეთრის ბალანსს.",
"LightCraft could not open its window: {e}": "LightCraft-მა ფანჯარა ვერ გახსნა: {e}",
"LightCraft could not open its window: {e}\n\nThe log file has the details: {path}": "LightCraft-მა ფანჯარა ვერ გახსნა: {e}\n\nდეტალები ჟურნალის ფაილშია: {path}",
"Lightroom catalog inspected: {photos} photos, {collections} collections, {missing} missing": "Lightroom-ის კატალოგი შემოწმდა: ფოტო: {photos}, კოლექცია: {collections}, აკლია: {missing}",
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "{path}-ისთვის Lightroom-ის გეომეტრია არ შენარჩუნდა; Guided Upright-ის შენახული გარდაქმნა არ არის მხარდაჭერილი.",
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "{path}-ისთვის Lightroom-ის გეომეტრია არ შენარჩუნდა; Upright-ის შენახული გარდაქმნა არ არის მხარდაჭერილი.",
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "{path}-ისთვის Lightroom-ის გეომეტრია არ შენარჩუნდა; შენახული გარდაქმნა იგნორირებულია, რადგან Upright გამორთულია.",
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "{path}-ისთვის Lightroom-ის გეომეტრია არ შენარჩუნდა; {mode} ხელახლა გამოითვლება.",
"Lightroom import complete: {imported} photos added": "Lightroom-იდან იმპორტი დასრულდა: დაემატა ფოტო: {imported}",
"Lightroom import complete: {imported} photos added, {failed} failed": "Lightroom-იდან იმპორტი დასრულდა: დაემატა ფოტო: {imported}, ვერ მოხერხდა: {failed}",
"Lightroom import complete: {imported} photos added; index warning: {warning}": "Lightroom-იდან იმპორტი დასრულდა: დაემატა ფოტო: {imported}; ინდექსის გაფრთხილება: {warning}",
"Lightroom import failed: {e}{warning}": "Lightroom-იდან იმპორტი ვერ მოხერხდა: {e}{warning}",
"Lightroom inspection failed: {e}{warning}": "Lightroom-ის შემოწმება ვერ მოხერხდა: {e}{warning}",
"MIT OR Apache-2.0. Fonts: {} (OFL). Icons: original.": "MIT OR Apache-2.0. შრიფტები: {} (OFL). ხატულები: ორიგინალური.",
"Making the denoised picture… {}%": "იქმნება ხმაურმოცილებული სურათი… {}%",
"Matches: {}": "დამთხვევა: {}",
"Merging… {stage} {:.0}%": "გაერთიანება… {stage} {:.0}%",
"Move {what} to Recently Deleted?": "გადავიდეს {what} ახლახან წაშლილებში?",
"Moved {} photo(s)": "გადატანილია ფოტო: {}",
"Moved; {} photo(s) relinked": "გადატანილია; ხელახლა დაკავშირდა ფოტო: {}",
"Named ({named_n})": "სახელიანი ({named_n})",
"New Folder in “{name}”": "ახალი საქაღალდე „{name}“-ში",
"On {have} of {of} selected photos": "{of} მონიშნული ფოტოდან {have}-ზე",
"Painting “{k}”: click photos to add or remove it · Esc stops": "„{k}“-ის მინიჭება: დაწკაპეთ ფოტოებზე მის დასამატებლად ან მოსახსნელად · Esc ასრულებს",
"Paste into {n} photo{} — only settings that were copied are pasted": "ჩასმა ფოტოებში: {n}{:.0} — ჩაისმება მხოლოდ კოპირებული პარამეტრები",
"Preset: {name}": "პრესეტი: {name}",
"Preview only — editing the camera's embedded JPEG ({})": "მხოლოდ წინასწარი ხედი — რედაქტირდება კამერის ჩაშენებული JPEG ({})",
"Preview only — {}": "მხოლოდ წინასწარი ხედი — {}",
"Previews ready for {done} photo{plural}": "წინასწარი ხედები მზადაა ფოტოებისთვის: {done}{plural:.0}",
"Profile: {}": "პროფილი: {}",
"Proof Copy ({})": "საცდელი ასლი ({})",
"Proof Preview · {}": "საცდელი ჩვენება · {}",
"Put inside “{parent}”": "„{parent}“-ში მოთავსება",
"Quick Develop · {n} photos": "სწრაფი დამუშავება · ფოტო: {n}",
"Quick Develop: {name}": "სწრაფი დამუშავება: {name}",
"Rated {}": "შეფასება: {}",
"Reading Lightroom catalog… {done} of {total}": "იკითხება Lightroom-ის კატალოგი… {done} / {total}",
"Reading photos… {done} of {total}": "იკითხება ფოტოები… {done} / {total}",
"Reading photos… {} of {}": "იკითხება ფოტოები… {} / {}",
"Redo {}": "{}-ის გამეორება",
"Remove “{name}”": "„{name}“-ის მოშორება",
"Remove “{name}” and its {count} photo{} from the library?": "მოშორდეს „{name}“ და მისი ფოტოები ({count}){:.0} ბიბლიოთეკიდან?",
"Remove “{}” from Favorites": "„{}“ რჩეულებიდან ამოშლა",
"Removed from {}": "ამოიშალა {}-დან",
"Removed {what} from {name}": "{what}: ამოიშალა {name}-დან",
"Rename {n} Photos…": "ფოტოების გადარქმევა: {n}…",
"Rename “{name}”": "„{name}“-ის გადარქმევა",
"Renames “{from}” on {n} photo{} (keywords below it too). Use | for levels, e.g. Travel|Italy. An existing name merges the two.": "„{from}“ გადაერქმევა ფოტოებზე ({n}){:.0} (მის ქვემოთ მყოფ საკვანძო სიტყვებზეც). დონეებისთვის გამოიყენეთ |, მაგ., მოგზაურობა|იტალია. არსებული სახელის შემთხვევაში ორივე გაერთიანდება.",
"Rendering on the CPU: {why}": "რენდერი CPU-ზე: {why}",
"Replace {} with:": "{}-ის ჩანაცვლება:",
"Reset {name}": "ჩამოყრა: {name}",
"Reset {} Channel": "არხის ჩამოყრა: {}",
"Restore {}": "აღდგენა: {}",
"Restore {} Photo{}": "ფოტოს აღდგენა: {}{:.0}",
"Restored {} photo{} from Recently Deleted": "ახლახან წაშლილებიდან აღდგა ფოტო: {}{:.0}",
"Running on the processor: {}.": "მუშაობს პროცესორზე: {}.",
"Running on {}.": "მუშაობს: {}.",
"Saved export preset “{}”": "ექსპორტის პრესეტი „{}“ შენახულია",
"Scores {scope} for focus and exposure and finds similar shots taken within seconds of each other (bursts).": "აფასებს ფოკუსსა და ექსპოზიციას ({scope}) და პოულობს ერთმანეთისგან რამდენიმე წამში გადაღებულ მსგავს კადრებს (სერიებს).",
"Show {hidden} hidden location{}": "დამალული მდებარეობების ჩვენება: {hidden}{:.0}",
"Showing {n} of {}": "ნაჩვენებია {n} / {}",
"Shows the model's terms, then downloads {} from {host}, installs it and starts using it. Nothing else is sent.": "აჩვენებს მოდელის პირობებს, შემდეგ ჩამოტვირთავს {}-ს {host}-დან, დააყენებს და დაიწყებს მის გამოყენებას. სხვა არაფერი იგზავნება.",
"Smart album: {}": "სმარტ-ალბომი: {}",
"Tagged {n} photo{} from the tracklog": "მარშრუტის ჟურნალით მოინიშნა ფოტო: {n}{:.0}",
"The SAM 3 download failed: {e}": "SAM 3-ის ჩამოტვირთვა ვერ მოხერხდა: {e}",
"There is a keyword set “{name}” already": "საკვანძო სიტყვების ნაკრები „{name}“ უკვე არსებობს",
"There is a “{name}” at the top level already. Merge the two?": "ზედა დონეზე „{name}“ უკვე არსებობს. გაერთიანდეს ორივე?",
"Trained on: {provenance}": "გაწვრთნილია: {provenance}",
"Undo {}": "დაბრუნება: {}",
"Unknown tag{s} {} stay{} as typed (see Tags)": "უცნობი ტეგი{s:.0} {} რჩება როგორც აიკრიფა{:.0} (იხ. „ტეგები“)",
"Updated “{name}”": "„{name}“ განახლდა",
"Version {} — a clean-room, pure-Rust photo library and raw developer.": "ვერსია {} — სუფთა ოთახის მეთოდით შექმნილი, სუფთა Rust-ზე დაწერილი ფოტობიბლიოთეკა და RAW-დამმუშავებელი.",
"Version: {}": "ვერსია: {}",
"Virtual copy “{name}” of {of}": "{of}-ის ვირტუალური ასლი „{name}“",
"Working on {what} for {} photos…": "{what}: მუშავდება ფოტოები ({})…",
"XMP adjustments were not imported for {path}: {fields}.": "{path}-ისთვის XMP-ის კორექციები არ შემოვიდა: {fields}.",
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "{path}-ისთვის XMP პროფილი „{profile}“ არ გამოიყენება; ფერები შეიძლება განსხვავდებოდეს Lightroom-ისგან.",
"none (CPU rendering): {why}": "არცერთი (რენდერი CPU-ზე): {why}",
"not added to Apple Photos: {e}": "არ დაემატა Apple Photos-ს: {e}",
"the {n} selected photos": "მონიშნული ფოტოები ({n})",
"the {} photos in view": "ხედში არსებული ფოტოები ({})",
"these {count} photos": "ეს ფოტოები ({count})",
"{:.1} MB": "{:.1} MB",
"{count} smart preview{} · {:.1} MB": "ჭკვიანი წინასწარი ხედი: {count}{:.0} · {:.1} MB",
"{done} of {total}": "{done} / {total}",
"{done} of {total} MB": "{done} / {total} MB",
"{file} · {size}": "{file} · {size}",
"{f} folders (and their subfolders)": "საქაღალდე: {f} (ქვესაქაღალდეებით)",
"{label} Grading": "გრადაცია: {label}",
"{label} {d:+} on every selected photo": "{label} {d:+} ყველა მონიშნულ ფოტოზე",
"{label} {index} of {n}": "{label} {index} / {n}",
"{label}, {n} photos": "{label}, ფოტო: {n}",
"{matching} of {t} photos": "ფოტო: {matching} / {t}",
"{matching} photos": "ფოტო: {matching}",
"{mode} spot {} of {}": "{mode}: ლაქა {} / {}",
"{month}": "{month:02}",
"{month} {year}": "{month:02}.{year}",
"{msg} ({failed} failed)": "{msg} (ვერ მოხერხდა: {failed})",
"{name} Label": "იარლიყი: {name}",
"{name} Off": "{name}: გამორთულია",
"{name} On": "{name}: ჩართულია",
"{name} opened for editing; it is stacked with the original": "{name} გაიხსნა რედაქტირებისთვის; ორიგინალთან ერთ დასტაშია",
"{name} opened in the other editor": "{name} გაიხსნა სხვა რედაქტორში",
"{name} · {terms}": "{name} · {terms}",
"{name} — double-click to reset it": "{name} — ორმაგი დაწკაპუნებით ჩამოყრა",
"{n} Local photo{} skipped: albums hold library photos (Add to My Photos first)": "გამოტოვებულია ლოკალური ფოტო: {n}{0:.0} — ალბომებში მხოლოდ ბიბლიოთეკის ფოტოებია (ჯერ დაამატეთ ჩემს ფოტოებში)",
"{n} added to Apple Photos": "დაემატა Apple Photos-ს: {n}",
"{n} added to Apple Photos album “{album}”": "დაემატა Apple Photos-ის ალბომს „{album}“: {n}",
"{n} change{} couldn't be written to disk: {e}\nIf you quit now, {} lost.": "დისკზე ვერ ჩაიწერა ცვლილება: {n}{0:.0}: {e}\nთუ ახლა გახვალთ, ისინი დაიკარგება.{1:.0}",
"{n} change{} saved in memory but not written to disk: {e}\nLightCraft retries automatically; quitting now would lose {}.": "ცვლილება შენახულია მეხსიერებაში, მაგრამ დისკზე არ ჩაწერილა: {n}{:.0}: {e}\nLightCraft ავტომატურად ცდილობს ხელახლა; ახლა გასვლით ისინი დაიკარგება.{:.0}",
"{n} change{} saved in memory but not written to disk: {e} — LightCraft will retry": "ცვლილება შენახულია მეხსიერებაში, მაგრამ დისკზე არ ჩაწერილა: {n}{:.0}: {e} — LightCraft ხელახლა სცდის",
"{n} found · {sel} selected": "ნაპოვნია: {n} · მონიშნულია: {sel}",
"{n} originals opened in the other editor": "სხვა რედაქტორში გახსნილი ორიგინალები: {n}",
"{n} photos": "ფოტო: {n}",
"{n} photos in {} are hidden by the filters above": "ზემოთ მოცემული ფილტრები მალავს „{}“-ის ფოტოებს: {n}",
"{n} photos opened for editing; each is stacked with its original": "რედაქტირებისთვის გაიხსნა ფოტო: {n}; თითოეული თავის ორიგინალთან ერთ დასტაშია",
"{n} photo{}": "ფოტო: {n}{:.0}",
"{n} photo{} match · updates automatically as photos change": "შესაბამისი ფოტო: {n}{:.0} · ავტომატურად განახლდება ფოტოების ცვლილებისას",
"{n} photo{} now · updates automatically as photos change": "ახლა ფოტო: {n}{:.0} · ავტომატურად განახლდება ფოტოების ცვლილებისას",
"{n} similar photo{} · View ▸ Clear Filters to see all": "მსგავსი ფოტო: {n}{:.0} · ყველას სანახავად: ხედი → ფილტრების გასუფთავება",
"{n} tasks are still running: {names}. Quitting stops them.": "ჯერ კიდევ მიმდინარეობს ამოცანები ({n}): {names}. გასვლა მათ შეწყვეტს.",
"{n} {noun}": "{noun}: {n}",
"{pct} %": "{pct} %",
"{pid} (missing)": "{pid} (აკლია)",
"{sel_n} selected · {counted}": "მონიშნულია: {sel_n} · {counted}",
"{size} GB": "{size} GB",
"{stage}… {:.0}%": "{stage}… {:.0}%",
"{verb} {n} Photo{}": "{verb} (ფოტო: {n}){:.0}",
"{weekday}, {day}": "{weekday}, {day}",
"{weekday}, {day} {month} {year}": "{weekday}, {day:02}.{month:02}.{year}",
"{what} merge added": "{what}: გაერთიანება დაემატა",
"{what} merge cancelled": "{what}: გაერთიანება გაუქმდა",
"{what} merge failed: {e}": "{what}: გაერთიანება ვერ მოხერხდა: {e}",
"{year}": "{year}",
"{{date:…}} directives: {}": "{{date:…}} დირექტივები: {}",
"{} KB": "{} KB",
"{} MB": "{} MB",
"{} and higher": "{} და მეტი",
"{} contributors · {} commits": "მონაწილე: {} · კომიტი: {}",
"{} failed": "ვერ მოხერხდა: {}",
"{} files": "ფაილი: {}",
"{} files and {f} folder{}": "ფაილი: {}, საქაღალდე: {f}{:.0}",
"{} item{}": "ელემენტი: {}{:.0}",
"{} label (click more labels to show any of them)": "იარლიყი „{}“ (დაწკაპეთ სხვა იარლიყებზე, რომ ნებისმიერი მათგანის მქონე ფოტო გამოჩნდეს)",
"{} of 4 guides — drag along lines that should be vertical or horizontal.": "დამხმარე ხაზი: {} / 4 — გაავლეთ ხაზებზე, რომლებიც ვერტიკალური ან ჰორიზონტალური უნდა იყოს.",
"{} of {} from {host}": "{} / {} — {host}-დან",
"{} photos": "ფოტო: {}",
"{} photos · exposures {} EV": "ფოტო: {} · ექსპოზიციები {} EV",
"{} photos · {} · {:.0}° × {:.0}°": "ფოტო: {} · {} · {:.0}° × {:.0}°",
"{} photo{} not readable": "ვერ იკითხება ფოტო: {}{:.0}",
"{} sample{}": "ნიმუში: {}{:.0}",
"{} sec": "{} წმ",
"{} spot(s) on this photo": "ლაქა ამ ფოტოზე: {}",
"{} · {}. Kept in the library's “denoise” folder; the oldest ones that no photo uses go first when the limit is reached.": "{} · {}. ინახება ბიბლიოთეკის „denoise“ საქაღალდეში; ლიმიტის მიღწევისას პირველად იშლება ყველაზე ძველი, არცერთი ფოტოს მიერ გამოუყენებელი ფაილები.",
"{} — show their photos": "{} — მისი ფოტოების ჩვენება",
"· {dups} already in the library": "· უკვე ბიბლიოთეკაშია: {dups}",
"· {in_source} identical copies": "· იდენტური ასლები: {in_source}",
"· {n} photo{}": "· ფოტო: {n}{:.0}",
"· {trashed} in Recently Deleted": "· ახლახან წაშლილებში: {trashed}",
"“{parent}” has a “{name}” already. Merge the two?": "„{parent}“-ში „{name}“ უკვე არსებობს. გაერთიანდეს ორივე?",
"• {assumption}": "• {assumption}",
"… and {more} more": "… და კიდევ {more}",
" in {g} group{}": " in {g} group{}",
" · kept, not applied: {}": " · kept, not applied: {}",
" · {n} skipped (could not be rendered)": " · {n} skipped (could not be rendered)",
" — kept but not applied: {}": " — kept but not applied: {}",
"Applied {name}": "Applied {name}",
"Apply to “{mask}” on Selected Photos": "Apply to “{mask}” on Selected Photos",
"Create the keyword “{name}”": "Create the keyword “{name}”",
"Delete the effect preset “{name}”?": "Delete the effect preset “{name}”?",
"Delete these {n} effect presets?": "Delete these {n} effect presets?",
"Delete {n} effect preset{}? This can't be undone.": "Delete {n} effect preset{}? This can't be undone.",
"Delete “{name}”?": "Delete “{name}”?",
"Effect: {name}": "Effect: {name}",
"Exported to {path}": "Exported to {path}",
"Exported {} effect preset{}": "Exported {} effect preset{}",
"Imported {e} mask effect{} (Masking ▸ Effect)": "Imported {e} mask effect{} (Masking → Effect)",
"Imported {name}": "Imported {name}",
"Keywords of {n} selected photos": "Keywords of {n} selected photos",
"LightCraft stopped unexpectedly: {e}": "LightCraft stopped unexpectedly: {e}",
"LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}": "LightCraft stopped unexpectedly: {e}\n\nThe log file has the details: {path}",
"Removed “{name}” from {n} photos. Edit ▸ Undo puts it back.": "Removed “{name}” from {n} photos. Edit → Undo puts it back.",
"Renames {n} face{} across the library. The files are not changed.": "Renames {n} face{} across the library. The files are not changed.",
"Saved template {name}": "Saved template {name}",
"Skipped {problem}": "Skipped {problem}",
"Slideshow exported: {what}": "Slideshow exported: {what}",
"Template preview: {name}": "Template preview: {name}",
"The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.": "The XMP sidecars of {n} photos couldn't be written (is their folder offline?). LightCraft keeps trying.",
"Update “{name}” with Current Settings": "Update “{name}” with Current Settings",
"Updated {name}": "Updated {name}",
"new, inside {parent}": "new, inside {parent}",
"{count} more warnings.": "{count} more warnings.",
"{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.": "{missing} originals are missing. Their catalog records are available; use Find Missing Photos to relink them.",
"{n} JPEG files": "{n} JPEG files",
"{n} pages": "{n} pages",
"{n} photos in view · {slides} slides · {time}": "{n} photos in view · {slides} slides · {time}",
"{n} preset{}": "{n} preset{}",
"{n} raws can now be developed from their raw data": "{n} raws can now be developed from their raw data",
"{photos} catalog records processed; {collections} collections.": "{photos} catalog records processed; {collections} collections.",
"{s} s of AV1 video": "{s} s of AV1 video",
"{what} merge added at {mp}, the largest a panorama can be (its full size is {full})": "{what} merge added at {mp}, the largest a panorama can be (its full size is {full})",
"{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has": "{what} merge added at {mp}: its full size, {full}, needs more memory than this computer has",
"“{to}” is already a person. Combining them makes one person; Undo separates them again.": "“{to}” is already a person. Combining them makes one person; Undo separates them again.",
"…and {more} more": "…and {more} more",
"Color space: Rec. 2020 {curve} (HDR AVIF)": "Color space: Rec. 2020 {curve} (HDR AVIF)",
"{n} selected photos · {pages} pages": "{n} selected photos · {pages} pages",
"Already in “{}”": "Already in “{}”",
"Move {n} photo{} to “{}”": "Move {n} photo{} to “{}”",
"Move {n} photo{} to “{}”?": "Move {n} photo{} to “{}”?",
"Moved {n} photo{} to “{}”. Edit → Undo moves them back.": "Moved {n} photo{} to “{}”. Edit → Undo moves them back."
}
File diff suppressed because it is too large. Load diff
+52
View File
@@ -129,6 +129,7 @@ language_table! {
Pl, "pl", "Polski", "Latn", include_str!("../locales/pl.json");
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");
}
// The settings file stores the BCP-47 code (`"zh-hans"`), never the Rust variant name, so a
@@ -226,6 +227,22 @@ pub fn stopped_message(error: &str, log_file: Option<&str>) -> (String, String)
(tr("LightCraft stopped").to_string(), text)
}
/// Whether sentence case may upper-case `c`. Georgian has no case: upper-casing Mkhedruli would
/// turn it into Mtavruli, a different (all-caps) script, so Georgian letters stay as written.
pub fn capitalizes(c: char) -> bool {
!matches!(c, '\u{10A0}'..='\u{10FF}' | '\u{1C90}'..='\u{1CBF}' | '\u{2D00}'..='\u{2D2F}')
}
/// `text` with its first letter upper-cased ("its input" → "Its input"), unless [`capitalizes`]
/// says that letter has no capital to take.
pub fn upper_first(text: &str) -> String {
let mut chars = text.chars();
match chars.next() {
Some(first) if capitalizes(first) => first.to_uppercase().chain(chars).collect(),
_ => text.to_string(),
}
}
pub fn builtin_label(source: &str, builtin: bool) -> &str {
if builtin { tr(source) } else { source }
}
@@ -517,6 +534,15 @@ mod tests {
set_language(Locale::En);
}
#[test]
fn sentence_case_keeps_georgian_lowercase() {
assert_eq!(upper_first("its input is not an image"), "Its input is not an image");
assert_eq!(upper_first("ფაილი ვერ იკითხება"), "ფაილი ვერ იკითხება");
assert_eq!(upper_first("ᲥᲐᲠᲗᲣᲚᲘ"), "ᲥᲐᲠᲗᲣᲚᲘ");
assert_eq!(upper_first(""), "");
assert!(capitalizes('a') && capitalizes('ж') && !capitalizes('ქ') && !capitalizes('Ⴀ') && !capitalizes('ⴀ'));
}
#[test]
fn every_language_has_a_catalog_covering_the_source() {
assert!(crate::i18n::Locale::En.catalog().is_empty(), "the source language needs no catalog");
@@ -755,6 +781,7 @@ mod tests {
("app.language.polish", Locale::Pl),
("app.language.czech", Locale::Cs),
("app.language.italian", Locale::It),
("app.language.georgian", Locale::Ka),
];
// One command per language, and every command reachable from the menu table.
assert_eq!(commands.len(), Locale::ALL.len());
@@ -981,6 +1008,31 @@ mod tests {
set_language(Locale::En);
}
#[test]
fn georgian_switches_formats_counts_and_dates() {
let mut app = crate::LightcraftApp::new(lightcraft_engine::Session::with_demo(), Default::default());
app.run("app.language.georgian", serde_json::json!({})).unwrap();
assert_eq!(app.ui.language, Locale::Ka);
assert_eq!(crate::menubar::checked(&app, "app.language.georgian"), Some(true));
assert_eq!(tr("Light"), "სინათლე");
assert_eq!(date_group_label("2026-09-20", false), "კვირა, 20.09.2026");
assert_eq!(date_group_label("2026-09", false), "09.2026");
for n in [0, 1, 2, 5, 21] {
assert_eq!(tr_format!("Imported {} photo{}", n, if n == 1 { "" } else { "s" }), format!("იმპორტირებულია ფოტო: {n}"));
}
assert_eq!(tr_format!("Added {n} photo{} to “{}”", "s", "Trip", n = 3), "„Trip“-ს დაემატა ფოტო: 3");
let saved = serde_json::to_string(&app.ui).unwrap();
assert!(saved.contains(r#""language":"ka""#));
for code in ["ka", "ka-GE", "ka_GE.UTF-8"] {
assert_eq!(Locale::parse_tag(code), Some(Locale::Ka));
}
// no Mtavruli: Georgian UI text is written in lowercase Mkhedruli
for text in Locale::Ka.catalog().values() {
assert!(!text.chars().any(|c| ('\u{1C90}'..='\u{1CBF}').contains(&c)), "{text}");
}
set_language(Locale::En);
}
#[test]
fn ukrainian_panels_dialogs_and_letters_paint_with_bundled_fonts() {
let ctx = fonts_ctx(&[]);
+2
View File
@@ -27,6 +27,7 @@ pub const LANGUAGE_COMMANDS: &[UiCommand] = &[
("app.language.polish", crate::i18n::Locale::Pl.name(), None, "Edit>Language"),
("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"),
];
/// Every UI command: the languages, then everything else. `xtask parity` reads both tables from
@@ -52,6 +53,7 @@ pub fn language_from_command(id: &str) -> Option<crate::i18n::Locale> {
"app.language.polish" => Some(crate::i18n::Locale::Pl),
"app.language.czech" => Some(crate::i18n::Locale::Cs),
"app.language.italian" => Some(crate::i18n::Locale::It),
"app.language.georgian" => Some(crate::i18n::Locale::Ka),
_ => None,
}
}
+1 -3
View File
@@ -606,9 +606,7 @@ pub(super) fn mb(bytes: Option<u64>) -> String {
pub(super) fn sentence(s: &str) -> String {
let s = s.trim();
let mut c = s.chars();
let mut out: String = c.next().map(|f| f.to_uppercase().collect()).unwrap_or_default();
out.push_str(c.as_str());
let mut out = crate::i18n::upper_first(s);
if !out.is_empty() && !out.ends_with(['.', '!', '?']) {
out.push('.');
}
+1 -3
View File
@@ -177,9 +177,7 @@ fn mb(bytes: Option<u64>) -> String {
/// "its input is not an image" → "Its input is not an image."
fn sentence(s: &str) -> String {
let s = s.trim();
let mut c = s.chars();
let mut out: String = c.next().map(|f| f.to_uppercase().collect()).unwrap_or_default();
out.push_str(c.as_str());
let mut out = crate::i18n::upper_first(s);
if !out.is_empty() && !out.ends_with(['.', '!', '?']) {
out.push('.');
}
+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", "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"] {
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(), 13, "a language added: list its file above");
assert_eq!(Locale::ALL.len(), 14, "a language added: list its file above");
}
}
+1 -1
View File
@@ -107,7 +107,7 @@ pub(super) fn choices<V: PartialEq + Copy>(ui: &mut egui::Ui, id: &str, options:
fn general_tab(app: &mut LightcraftApp, ui: &mut egui::Ui, t: &Tokens) {
row(ui, t, crate::i18n::tr("Language"), |ui| {
let languages: Vec<_> = crate::i18n::Locale::ALL.iter().map(|language| (*language, language.name())).collect();
// Twelve language names do not fit one line in a narrow window: wrap them.
// Thirteen language names do not fit one line in a narrow window: wrap them.
ui.horizontal_wrapped(|ui| {
choices(ui, "settingsLanguage", &languages, &mut app.ui.language);
});
+77
View File
@@ -0,0 +1,77 @@
# LightCraft in Georgian
Choose **Edit → Language → ქართული** or **Settings → General → Language**. The choice applies at once
and is saved for the next launch (`language: "ka"` in `ui.json`); the control command is
`app.language.georgian`. `LIGHTCRAFT_LANGUAGE` selects it for headless runs and accepts `ka`, `ka-GE`
and `ka_GE.UTF-8`; the saved value is always `ka`. LightCraft does not follow the system language,
for Georgian as for every other language.
The Georgian catalogs cover every key of the other catalogs plus all command and slider labels:
menus, shortcuts, library, editing, crop, masks, people, import/export, settings, built-in presets and
profiles, history, versions, date headings, status messages, the smart-album rule editor and the
unsaved-changes dialog. The embedded release notes are translated too. Technical errors from lower
layers stay in English, as in the other languages. They were written from the meaning of the English
labels; no proprietary translation resources were consulted.
File names, command ids, template placeholders and albums, presets or metadata you named yourself are
not translated. Descriptions for SAM 3 go to the model unchanged.
## Georgian script
Mkhedruli has no upper and lower case: `i18n::upper_first` leaves Georgian letters alone, and the
interface never uses Mtavruli (a test checks the whole catalog). Commands are verbal nouns. Counts
are count-neutral, noun first (`იმპორტირებულია ფოტო: 3`); English plural endings are dropped with
`{:.0}`. Dates read `კვირა, 20.09.2026` (`{weekday}, {day:02}.{month:02}.{year}`), months `09.2026`.
Inter has no Georgian letters and craft-fonts has no Georgian face yet, so Georgian text shows boxes
until a Georgian face is available to the UI.
## Terminology
| Source | Georgian |
|---|---|
| Library / Develop | ბიბლიოთეკა / დამუშავება |
| File / Edit / View | ფაილი / რედაქტირება / ხედი |
| Settings / General / Language | პარამეტრები / ზოგადი / ენა |
| Import / Export | იმპორტი / ექსპორტი |
| Photos / Albums / Folders | ფოტოები / ალბომები / საქაღალდეები |
| Smart Album | სმარტ-ალბომი |
| Crop | ჩამოჭრა |
| Masks / Masking | ნიღბები / ნიღბვა |
| Heal / Remove | აღდგენა / მოშორება |
| Presets / Profile | პრესეტები / პროფილი |
| History / Versions | ისტორია / ვერსიები |
| Light / Color / Effects / Detail | სინათლე / ფერი / ეფექტები / დეტალი |
| Exposure / Contrast | ექსპოზიცია / კონტრასტი |
| Highlights / Shadows | ღია ტონები / ჩრდილები |
| Whites / Blacks | თეთრები / შავები |
| Texture / Clarity / Dehaze | ტექსტურა / სიცხადე / ბურუსის მოცილება |
| Vibrance / Saturation | სიხალასე / გაჯერება |
| Hue / Luminance | ფერის ტონი / სიკაშკაშე |
| Tint | ტონირება |
| Tone Curve / Color Grading | ტონალური მრუდი / ფერის გრადაცია |
| Sharpening / Noise Reduction | გამკვეთრება / ხმაურის შემცირება |
| Lens Corrections / Geometry | ობიექტივის კორექციები / გეომეტრია |
| Vignette / Grain | ვინიეტი / მარცვლოვნება |
| Feather / Threshold | კიდეების შერბილება / ზღვარი |
| Rating / Flag / Keywords | შეფასება / დროშა / საკვანძო სიტყვები |
| Reset / Revert | ჩამოყრა / წინა ვერსიაზე დაბრუნება |
| Undo / Redo | დაბრუნება / გამეორება |
Please follow this table and the shared ArtCraft Georgian glossary when you add strings.
## Maintenance and checks
Fixed texts: `crates/ui-egui/locales/ka.json` (2,719 entries). Messages with values:
`crates/ui-egui/locales/ka-formats.json` (249 entries). The English keys stay unchanged; Rust checks
the placeholders at build time. Strings not yet in the plain catalog show in English.
```sh
cargo test -p lightcraft-ui-egui i18n::tests -- --nocapture
LIGHTCRAFT_LANGUAGE=ka lightcraft-cli snapshot --demo -o georgian.png --size 1600x1000
```
The tests check catalog coverage, placeholders, switching language by menu and control channel,
saving, the `ka` locale tags, dates, count wording, the absence of Mtavruli and that sentence case
keeps Georgian lowercase. More notes are in
[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 interface catalog added) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6
> **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
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)).
[ru](localization-ru.md), [ka](localization-ka.md)).
## The target
@@ -66,6 +66,7 @@ that is promising but is not recorded as review, so the column says `no (communi
| Russian | `ru` | 1,704 / 2,304 (74%) | Cyrillic (Inter) | no (community edits) | partial | 8–12 h |
| 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) |
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
@@ -94,5 +95,6 @@ Target languages LightCraft lacks: Korean, Dutch, Polish, Swedish, Norwegian Bok
|---|---|---|
| 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 |
| 2026-10-10 | major | Created: per-language counts measured from the catalogs, the twelve key languages, script support, estimates |
| 2026-10-12 | minor | de, es, fr, ja and uk catalogs filled to every message another catalog knows (agent translations, native review welcome); those rows re-measured against the current 2,398 messages. Other rows keep their 2026-10-10 counts (open PRs for pt-BR, ru, zh) |
+7 -4
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`) and Czech (`cs`). 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`) and Georgian (`ka`). 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) and
[`localization-cs.md`](localization-cs.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).
## Adding a language
@@ -33,6 +33,7 @@ are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-h
Uk, "uk", "Українська", "Cyrl", include_str!("../locales/uk.json");
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");
}
```
@@ -46,7 +47,9 @@ are in [`localization-zh-hans.md`](localization-zh-hans.md), [`localization-zh-h
3. **Make sure the glyphs exist.** The script drives which craft-fonts faces the UI installs: a
language whose script has no face in the build shows boxes (the test says so instead of failing).
Fonts live in [storytold/craft-fonts](https://github.com/storytold/craft-fonts), never here; a
missing face is added there, not worked around in this repository.
missing face is added there, not worked around in this repository. Sentence-cased messages go
through `i18n::upper_first`, which leaves Georgian lowercase (upper-casing Mkhedruli would make
Mtavruli).
4. Run `cargo xtask ci` (it includes `fmt`, `clippy`, the tests, `parity` and the wasm check).
## How a message is found
+2 -2
View File
@@ -480,8 +480,8 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts).
| LR-BEHAV-TEXTFIELD | Text fields behave alike | P1 | 🟡 | `crates/ui-egui/src/text_field.rs`, `crates/ui-egui/src/panels/topbar.rs`, `crates/ui-egui/src/widgets.rs`, `crates/ui-egui/src/panels/right.rs` (`meta_field`), `crates/ui-egui/src/panels/dialogs.rs`, `crates/ui-egui/src/panels/unnamed.rs` | one shared field: right-click Cut / Copy / Paste / Select All (no Paste in the browser), Esc gives back the text from before the edit, Return / leaving / Esc reported to the caller, optional select-on-focus; agents use `ui.clipboard` (MCP `clipboard`) and `ui.inspect` → `copied`. Used by Search Photos, slider values, the Info panel's metadata fields (multi-line captions), the one-field dialogs (New / Rename Album, New Smart Album, Rename / Merge Keywords, text prompts) and the unnamed faces' name field so far; the other text fields (most dialogs, import, settings, masking, keywords …) still use egui's own and have no menu |
| LR-BEHAV-HEADLESS | Headless UI snapshots | P2 | ✅ | `apps/lightcraft-cli/src/main.rs`, `crates/ui-egui/src/headless.rs`, `docs/control-protocol.md` | `snapshot` rasterizes the UI and defaults photo development to the CPU before the first frame, without adapter discovery; scaled dimensions are validated before allocation, and UI zoom preserves the requested physical viewport; JSON-lines control scripts are supported |
| LR-BEHAV-ACCESS | Accessibility | P2 | 🟡 | `crates/ui-egui/src/widgets.rs`, `crates/ui-egui/src/text_field.rs`, `crates/ui-egui/src/panels/grid.rs`, `crates/ui-egui/tests/accessibility.rs` | AccessKit (VoiceOver / Narrator / AT-SPI): sliders announce control, value, range and step and take Increment / Decrement, buttons / icon buttons / dropdowns / section headers / sources their labels and state, text fields their caption, grid thumbnails file name, rating, flag and label. `tests/accessibility.rs` fails on an unnamed control, a slider without range or an untitled dialog in every view, Develop tool and 13 dialogs, and checks that Crop and Remove sliders are reached with Tab and that Tab walks the right panels top to bottom. Canvas tools by keyboard (`crates/ui-egui/src/canvas_keys.rs`, `crates/ui-egui/src/tests_canvas_keys.rs`): crop corners, edges and box, spot targets and sources, a New spot point (Return adds), radial / linear gradient handles and mask pins take the focus, are announced with their position and move with the arrows (Shift: further). Brush strokes and red-eye ellipses are pointer-only; not tried with a real screen reader ([audit](ui-parity.md#accessibility-audit)) |
| LR-BEHAV-LOCALE | Language options | P2 | ✅ | `cmd:app.language.english`, `cmd:app.language.simplifiedChinese`, `cmd:app.language.traditionalChinese`, `cmd:app.language.japanese`, `cmd:app.language.portuguese`, `cmd:app.language.french`, `cmd:app.language.spanish`, `cmd:app.language.german`, `cmd:app.language.russian`, `cmd:app.language.ukrainian` | Edit > Language lists every language in the table and the active one is checked; shared with Settings > General (docs/localization.md) |
| LR-BEHAV-LOCALIZE | Localisation | P2 | 🟡 | `crates/ui-egui/src/i18n.rs`, `crates/ui-egui/locales/`, `docs/localization.md`, `docs/localization-zh-hans.md`, `docs/localization-zh-hant.md`, `docs/localization-ja.md`, `docs/localization-pt-br.md`, `docs/localization-es.md`, `docs/localization-de.md`, `docs/localization-ru.md` | One table entry per language (code, endonym, ISO 15924 script, catalog): English, Simplified Chinese, Traditional Chinese (Taiwan), Japanese, Brazilian Portuguese, Spanish, German, Russian and Ukrainian UI with a persisted language preference and craft-fonts regular/bold faces per script (a variable face, as for Arabic, is drawn heavier for bold from its one file); menus, edit, crop/masks, settings, import/export and primary progress messages. Adding a language registers the locale and its language command, plus two catalogs. Stock presets/profiles, history steps, library headings, date headings and capture times are localised (user-named items stay verbatim); the Ukrainian catalog covers every existing catalog key, command/control label and rule-editor label; the German catalog reports newly added gaps; the other catalogs fall back to English for messages they lack. Technical errors remain English; release notes are translated in German and Ukrainian; craft-fonts faces for other scripts (Thai, Arabic, …) are UI fallbacks in every language, for file, folder and album names (#736); Traditional Chinese borrows the Simplified Chinese face (no TC face in craft-fonts yet), and the web build embeds the Japanese face in WASM while loading the Simplified Chinese face from a separate font file. |
| LR-BEHAV-LOCALE | Language options | P2 | ✅ | `cmd:app.language.english`, `cmd:app.language.simplifiedChinese`, `cmd:app.language.traditionalChinese`, `cmd:app.language.japanese`, `cmd:app.language.portuguese`, `cmd:app.language.french`, `cmd:app.language.spanish`, `cmd:app.language.german`, `cmd:app.language.russian`, `cmd:app.language.ukrainian`, `cmd:app.language.georgian` | Edit > Language lists every language in the table and the active one is checked; shared with Settings > General (docs/localization.md) |
| LR-BEHAV-LOCALIZE | Localisation | P2 | 🟡 | `crates/ui-egui/src/i18n.rs`, `crates/ui-egui/locales/`, `docs/localization.md`, `docs/localization-zh-hans.md`, `docs/localization-zh-hant.md`, `docs/localization-ja.md`, `docs/localization-pt-br.md`, `docs/localization-es.md`, `docs/localization-de.md`, `docs/localization-ru.md`, `docs/localization-ka.md` | One table entry per language (code, endonym, ISO 15924 script, catalog): English, Simplified Chinese, Traditional Chinese (Taiwan), Japanese, Brazilian Portuguese, Spanish, German, Russian, Ukrainian and Georgian UI with a persisted language preference and craft-fonts regular/bold faces per script (a variable face, as for Arabic, is drawn heavier for bold from its one file); menus, edit, crop/masks, settings, import/export and primary progress messages. Adding a language registers the locale and its language command, plus two catalogs. Stock presets/profiles, history steps, library headings, date headings and capture times are localised (user-named items stay verbatim); the Ukrainian catalog covers every existing catalog key, command/control label and rule-editor label; the German catalog reports newly added gaps; the other catalogs fall back to English for messages they lack. Technical errors remain English; release notes are translated in German and Ukrainian; craft-fonts faces for other scripts (Thai, Arabic, …) are UI fallbacks in every language, for file, folder and album names (#736); Traditional Chinese borrows the Simplified Chinese face (no TC face in craft-fonts yet), and the web build embeds the Japanese face in WASM while loading the Simplified Chinese face from a separate font file. |
| LR-BEHAV-LEARN | Tutorials | OOS | 🚫 | | |
| LR-BEHAV-WHATSNEW | What's new | P2 | ✅ | `cmd:app.whatsNew`, `docs/whats-new.md` | Help ▸ What's New: release highlights |
| LR-BEHAV-AI-EA | Early-access badges | P2 | ⬜ | | |
+1 -1
View File
@@ -77,7 +77,7 @@ calibration: M0–M13 took ≈ 25 active hours against an estimate of ≈ 110–
| M13 | Merge | HDR merge (deghost), panorama (projections, boundary warp, fill edges), HDR panorama | 10–15 | ✅ |
| M14 | Video | import/playback/trim via FilmCraft crates, global edits + presets on video, video export | 6–10 | ⬜ |
| M15 | Classic modules | Map, Book, Slideshow, Print, Web; smart collections, stacks, virtual copies, publish services, tethering | 25–40 | 🚧 (smart albums, stacks, virtual copies, compare/survey ✅; contact-sheet PDF 🟡; Map/Book/Slideshow/Print/Web ⬜) |
| M16 | 1.0 polish | preferences, shortcut editor, accessibility, localization, packaging (dmg/msi/AppImage/web), hardening | 10–20 | 🚧 (settings, keyboard shortcuts sheet, packaging basics ✅; English/Chinese (Simplified, Traditional)/Japanese/Brazilian Portuguese/Spanish/German/Russian/Ukrainian localisation 🟡; accessibility and further locales ⬜) |
| M16 | 1.0 polish | preferences, shortcut editor, accessibility, localization, packaging (dmg/msi/AppImage/web), hardening | 10–20 | 🚧 (settings, keyboard shortcuts sheet, packaging basics ✅; English/Chinese (Simplified, Traditional)/Japanese/Brazilian Portuguese/Spanish/German/Russian/Ukrainian/Georgian localisation 🟡; accessibility and further locales ⬜) |
## Risks that coding hours alone don't retire