* fix(windows): retry busy download renames and log Windows Installer runs
Antivirus scanners often hold a just-written installer open, so renaming
the finished .part file failed with "Access denied (os error 5)". Retry
the rename for a few seconds on Windows errors 5 and 32.
Windows Installer error 1603 carries no detail on its own: run msiexec
with a verbose log beside the package and name it in the error.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* fix(download): honour pause and cancel while retrying a busy rename
Check the stop flag before every rename attempt, so a cancel that lands
while the checksum is being computed, or during a Windows retry backoff,
stops the download instead of installing it. A cancelled download still
throws its partial file away.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
#663 reworded the settings dialog's note to "On Linux, custom folders use
AppImage format." The meaning is unchanged, so each catalog's existing
translation is reused under the new key. The old wording is still used in
dialogs.rs, so its rows stay.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#677 added "A new version is available. Download it to replace this
version?" without catalog rows, so complete_catalogs_cover_every_ui_literal
failed on main and those languages showed English. Adds the row to all ten
complete catalogs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Craft App releases ship <app>-<ver>-freebsd-aarch64.tar.gz next to the
x86_64 tarball, but the platform tokens had none for FreeBSD on ARM64, so
nothing was picked there and the backend's kept-download tests failed on
the nightly FreeBSD aarch64 job. Pick freebsd-aarch64 on FreeBSD ARM64 the
way freebsd-x86_64 is picked on x86_64 (no cross-arch fallback), with a
core test on SoundCraft v0.10.0's release metadata trimmed to its FreeBSD
tarballs, and add the token to the backend tests' synthetic release.
The inherited-pipe test gave its direct child one second to start a second
test process and fill both pipes; under emulation that takes longer, so
run() killed the child first and the test never reached the case it
checks. Retry with a longer deadline (1 s, 4 s, 16 s) when the child didn't
get that far, let the descendant hold the pipes for 120 s unless stopped,
and still require run() to return within twice its deadline.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* fix(linux): only update release Arch packages and delegate AUR/repo packages (#586)
- Only consider release Arch packages (bundle ID starting with 'ai.storyteller.') eligible for launcher updates.
- Keep AUR packages (<app>, <app>-bin, <app>-git) directed to the user's AUR helper (yay/paru).
- Direct repository packages (e.g. Omarchy) to 'sudo pacman -Syu' and add translations across all 10 locales.
- Only self-update launcher if installed as the release Arch package ('ai.storyteller.artcraft-launcher'), preventing conflicts on AUR installs.
- Check pacman DB presence before assuming ArchPkg availability.
* fix(linux): exclude ArchPkg from candidates for non-release Pacman installs
* i18n: add AUR update hint translations across all catalogs
* fix(linux): return empty kinds for AUR packages, preserve can_pacman fallback, and bound pacman queries
* fix(linux): tighten arch package self-update check, streamline hint message, and test deterministically
* fix(linux): require arch availability for new preferred installs
egui decodes the colour picture on a background thread the first time it is
asked for, so the first hover over a card showed no icon for a moment, and the
grey-card test (8 frames, no real time) failed on CI's slower Windows runner.
The grey path now starts that load, and the test waits for the decode.
- Only consider release Arch packages (bundle ID starting with 'ai.storyteller.') eligible for launcher updates.
- Keep AUR packages (<app>, <app>-bin, <app>-git) directed to the user's AUR helper (yay/paru).
- Direct repository packages (e.g. Omarchy) to 'sudo pacman -Syu' and add translations across all 10 locales.
- Only self-update launcher if installed as the release Arch package ('ai.storyteller.artcraft-launcher'), preventing conflicts on AUR installs.
- Check pacman DB presence before assuming ArchPkg availability.
Main's Arch package option (#565) went into the Linux install-format list in
dialogs.rs, which this branch moved to settings_dialog.rs; it is added there.
* Drain helper output after reaching the capture limit
* fix: enforce helper deadline while draining inherited pipes
Keep both reader completions under the original shared timeout after the direct child exits. Add a finite self-spawned descendant regression that fails before the fix. Fresh isolated module: 6 tests passed, 2 ignored fixture entry points; strict Clippy and formatting passed. Independent review approved. Timeout detaches readers as before; no descendant-tree cancellation or lifetime resource-cleanup claim. AI assistance: OpenAI Codex for @MMexicann.
---------
Co-authored-by: skylar taylor-barrick <[email protected]>
jiff caches only a zone it found, so an unreadable one (a bad TZ, a bare
container) was searched for on every date of every frame. local_time keeps the
last answer either way for five minutes. Settings' two columns borrow their
shared state with try_borrow_mut, and the app page's next/previous use get().
- theme.rs gains the layout block: PAD, COLUMN_GAP, BOX_MARGIN, the toolbar,
dialog and page widths that change the layout, the three title sizes, and the
icon-link padding and gap. The app page, Settings, release notes and the small
prompts read from it instead of their own 12/14/16/24/32s.
- One brand-coloured scroll bar (sheet::accent_scroll takes the colour), used by
the app page too instead of a copy.
- An app that is not installed draws its text and accent in the theme's muted
grey; only the icon is a grey copy of itself.
- The compact toolbar puts "Sort" before its picker, like the full row; refresh
lights up on hover like the grid/table switch beside it.
- The grid keeps its sort rule in one place (views::grid_sort_now), and only the
grid groups installed apps first; the table keeps its column's order.
- icon_links owns its spacing, so neighbouring targets never touch, and Settings
sizes its icon row with icon_links_width.
- ATTRIBUTION.md lists discord.svg; a stale comment about the grid's sorts is gone.
install_dmg gained a replace parameter in #461; the macOS-only self-update call and its test
weren't updated, so the 0.5.0 release's macOS job failed to compile. Self-update writes over the
launcher's own bundle, which copy_app accepts without replace, so pass false.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cards wrote "Last updated" as a fixed 07/10/2026 while the table, app page and
release notes said "Oct 7, 2026", and every date was the UTC day, so a release
published late on the 7th UTC read as the 7th in Tokyo. local_time::civil adds
the system zone's offset at that instant (jiff on the desktop, which re-reads the
zone every few minutes; the browser's Date on the web) and falls back to UTC
when the zone can't be read. date_text is the one formatter.
Header: the Discord link and the refresh button leave the top bar; refresh sits in the toolbar and
Settings stays on the right.
Toolbar: the filter tabs go (installed apps come first, the rest under a Not installed heading); a
sort dropdown (Name A-Z / Z-A, Recently updated, Frequently used), search, Update all and the
grid/table switch drawn as two icons.
Cards and table: the version sits in the strip, apps that are not installed are drawn in grey until
hovered, the headline and last-updated date replace the status sentence; the table's two version
columns become one.
Footer: Website, GitHub and Discord join the social links as icons with more room between them.
App page: pictures, story and facts re-laid out for windows with and without screenshots.
Dialogs: Settings and release notes become large cards like the app page (a header with the brand
colour, a scrolling body, a pinned footer), Settings in boxed groups, two columns when wide.
Also adds core::release::safe_web_link, so links in release notes open only when they are plain https.
* linux: support self-update and restart for Flatpak launcher
Enable self-updating and restarting when ArtCraft Launcher runs inside a Flatpak container.
- Add `Install::Flatpak` to detect user Flatpak bundle installations via sandbox detection, app-path inspection in /.flatpak-info, and host flatpak-spawn availability.
- Cache `process::is_flatpak_available()` via `OnceLock` to prevent redundant host probes.
- Reinstall the downloaded .flatpak bundle with `--reinstall` through the Flatpak host portal.
- Schedule a detached relaunch helper on the host via flatpak-spawn that monitors the running Flatpak container instance/host PID until exit and relaunches via `flatpak run`, timing out after 60s without launching duplicate instances if exit fails.
- Add test coverage for Flatpak instance metadata and PID resolution.
* linux: detect the Flatpak bundle off the startup path
- Work out how the launcher is installed on the check thread, just before a
newer launcher is announced, instead of in NativeBackend:🆕 in the
Flatpak it asks the host's flatpak, which held up the first frame. The UI
re-reads its capabilities on LauncherUpdate.
- Self-update only a per-user copy whose origin is the bundle's own
(artcraft-launcher[N]-origin), asked through `flatpak info --show-origin`;
/.flatpak-info doesn't record it. A copy from a repository keeps Download:
an unsigned bundle can't go into a signed remote and would swap the build
under an unsigned one.
- Read app-path for ~/.local/share/flatpak only; XDG_DATA_HOME points into
~/.var/app inside the sandbox, so dirs::data_local_dir never matched.
- Don't relaunch blind when neither the host PID nor the instance id is known.
- Drop the cached flatpak probe: nothing on the startup path calls it now.
* fix(update): prevent accidental downgrades when releases or repos lag
---------
Co-authored-by: skylartaylor <[email protected]>
* feat(install): choose where Craft apps are installed
Settings gets a default install folder, and a fresh install asks for the
app's folder (prefilled with the default). Updates stay beside the copy
they replace. Works for MSI (INSTALLFOLDER), DMG bundles, and AppImages;
on Linux a custom folder means AppImage. Flatpak and the web build keep
the system location and skip the prompt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(windows): integrate portable installs with custom locations
* fix(install): ask before writing over something already in the install folder
Staging used a predictable `.<name>.partial` path beside the destination, so a
file the user kept under that name could be overwritten (AppImage) or deleted
(macOS). Stage in a fresh private folder instead.
A new install whose destination already exists now stops before downloading
and asks "Replace what's there?". Replace installs over it: the AppImage or
.app is swapped in, a portable install writes over same-named files only, and
the MSI installs into the existing folder. Cancel leaves everything as it was.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(install): a planted .partial symlink can't redirect AppImage staging
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Skyfuller1303 <[email protected]>
Reserve an exclusively owned backup directory while replacing an installed app. Preserve neighboring .old files and directories, and retain the old copy with its recovery path if rollback fails.
Includes regression coverage for backup collisions and failed rollback.
PR #332.
Present Windows DX12 through DirectComposition while retaining the presentation-system override and existing graphics selection.
Use whole-pixel glyph placement for the bundled fonts and inset the card hover rule within its hairlines.
Refs #339.
* Add FigCraft to the launcher
FigCraft (storytold/figcraft): interface design and prototyping, the
thirteenth Craft App and the sixth in the New group.
- Catalog row (brand colour #02da96 from FigCraft's icon README), details
page (no pictures yet), Linux desktop categories, icon tables, tagline
in every catalog, xtask local mirror list.
- Icon copied unmodified from FigCraft's repo; ATTRIBUTION.md row.
- The Windows installer art fits five rows of icons.
- Test that a companion figcraft-server tarball is never picked as the app.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Release 0.4.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
The pictures repo was renamed from artcraft-launcher-assets to match craft-launcher. GitHub
redirects the old name for now, but the launcher should not depend on that.
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Add a hand-written details page for each Craft App
core::details holds, for every app in the suite, a headline, a few
paragraphs, a list of features, where it runs and the list of its
pictures. The copy is the launcher's own: written from each app's page on
getartcraft.com and, for the five newer apps, from its README. It is
compiled in, so it works offline and in the web build, and it leaves out
figures that go stale (effect counts, timings).
The pictures are not in this repository. A page names them (a key and a
caption each) and AppDetails::screenshot_url turns a key into the address
of a 1200 px JPEG in storytold/artcraft-launcher-assets, pinned to a tag
(SCREENSHOTS_REV) so a saved copy never goes stale. It only answers for
keys the page lists, so no address is ever built from anyone else's text.
MAX_SCREENSHOT_BYTES caps what is accepted, and looks_like_jpeg refuses a
login page or error page that arrives as a 200.
PdfCraft lists none yet: its screenshots still show its old name
(PrintCraft). ATTRIBUTION.md points to the pictures repository, which
carries each picture's own provenance.
Tests check that every app has exactly one page, that each is filled in,
and that addresses come only from a page's own pictures.
* Open an app's details in a drawer from its card
Clicking a card (or an app's name in the table) slides a drawer in from
the right over the dimmed grid. It shows the app's headline, version and
main button, links to its website, GitHub and releases, a gallery of
screenshots, its features, the facts and notes of its latest release, and
the same trademark notice as the grid. Esc, the close button or a click
on the grid closes it. The page's text is the hand-written copy in
core::details; the version, date, download size and notes come from the
release the launcher already fetches.
The pictures are fetched when a page is opened, not bundled. The UI sends
Command::FetchScreenshots and gets one Event::Screenshot per picture, the
lead one first. The desktop backend downloads them through the launcher's
Http (its User-Agent, a 2 MB cap, redirects followed) and keeps them under
<cache>/screenshots/<revision>/<app>/<key>.jpg, so later opens, and any
time offline, read from disk. Only a JPEG is kept: a login page or error
page that arrives as a 200 is refused. A missing picture doesn't hold the
others back, but once the network has failed the rest fail at once instead
of each waiting for its own timeout. The web build does the same with
fetch and leaves the caching to the browser.
Until a picture is here, a frame of the same size says "Loading pictures…"
or, when it failed, that it couldn't be loaded and that reopening the page
tries again; nothing moves when the picture arrives. The lead picture
opens full-window, the arrows over the pictures and the arrow keys flip
through them, wrapping around, and a scroll bar stays visible (egui's
floating one only shows under the pointer).
The drawer's labels are tl! strings, with 13 rows in each of ru, zh-CN,
de, fr and pt-BR; details.rs joins the sources the catalog coverage test
reads. Every size taken from the window is kept non-negative: the new
tests resize it from 40 x 30 to 4000 x 2400 with the drawer open.
actions() becomes actions_in(), which can leave the links out of the
... menu where the page already shows them as buttons. The image crate's
jpeg feature is enabled to decode the pictures (zune-jpeg and zune-core
join Cargo.lock). The dim and the enlarged picture's backdrop are colours
in theme.rs.
* Bring the details drawer up to date with main
- Website is always a button now (#198); drop the "Like {name}" line and the trademark notice,
which main removed with the product comparisons (93caf01).
- Bring back widgets::eyebrow for the drawer's section headings (#187 dropped its last caller).
- The drawer's ⋯ menu leaves out only the links, so Hide/Unhide (#185) stays.
- No slide when the OS asks for reduced motion (#201).
- Translate the screen readers' "{name} details" label, and add the drawer's strings to the
Spanish, Czech, Polish, Hungarian and Japanese catalogs.
- AGENTS.md: name the pictures' download origin.
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Add a Polish interface
- pl.tsv: the full catalog (287 rows), written clean-room from the English
labels; plurals use the same one|few|many rule as Russian.
- Registered as the seventh language: "Polski" in the picker, locales pl,
pl-PL and pl_PL.UTF-8, complete_menus = true so the completeness tests
cover it.
- tests/polish.rs: the Polish UI test, modeled on the French one.
- README and AGENTS updated for the new language.
* Add Hungarian localization and fix responsive settings layout
* i18n: add Czech (cs) interface language
Czech catalog with every key of the most complete catalog (287 rows), the
cs language entry with locale detection (cs-CZ, cs_CZ.UTF-8, ?lang=cs),
a Czech plural rule (1 / 2-4 / other) with a unit test, and the README
language list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: add Japanese (ja) interface language
ja.tsv translates the whole interface, the tray menu and tooltip, each app's
tagline and the backend's install/update messages into Japanese, and
Settings > Language offers 日本語. Japanese locales (ja, ja-JP, ja_JP.UTF-8…)
select it; Japanese has one plural form.
Japanese is a CJK language, so cjk_font.rs now looks for a Japanese system
font (Hiragino Sans on macOS; Yu Gothic, Meiryo or MS Gothic on Windows;
Noto Sans CJK JP, Noto Sans JP, IPAGothic, Takao or VL Gothic on Linux and
FreeBSD), falling back to a Chinese one, which draws kana and most kanji,
where the system has none. The font is loaded per language, so switching
between Japanese and Chinese reloads the right face.
A fallback face is nudged onto the body font's baseline: egui puts a fallback
glyph's baseline at that face's own ascent and only centres the two line
boxes, which left a face whose metrics differ from Inter's riding high. On
Windows, Yu Gothic's line gap is half an em, so its kana and kanji sat about
0.29 em above the Latin text beside them (Microsoft YaHei, used for Chinese,
happens to match). The nudge, applied through a FontTweak, is the difference
in ascent and line height, and helps the Chinese faces too.
The README and AGENTS list the new language. Tests: the complete-catalog
checks cover ja.tsv (placeholders, plural forms, every tl! literal, taglines,
package formats), plus a locale/plural test, a Japanese glyph-coverage test,
a baseline test and tests/japanese.rs (grid, table, dates, a backend message).
* i18n: make the CJK font choice language-neutral
The CJK comments, the missing-font note and the plural-rule docs named the
languages, so each new one meant editing them all; they now say "CJK". Each
language's writing system (its `script`) now lives in the registry, and a
language is drawn only with a face for its own script — never another's,
which would leave characters as tofu — so a language the system has no font
for stays in English, like any other. Adding Korean or Traditional Chinese is
a registry row and a candidate list, not a special case.
* Hide Craft apps the user is not interested in.
* Read the Linux desktop appearance portal so system theme matching works on Wayland.
* UI: follow the OS's animation setting
With Windows' "Animation effects" switched off, the launcher still glided each
notch of the mouse wheel over several frames and faded its dropdowns in. egui
does both by default, and nothing asked the OS.
The shell now says what the OS wants (Host::reduce_motion) and the UI asks it
about once a second while it draws, so a change shows without a restart. With
fewer animations asked for:
- a mouse wheel scrolls at once. egui has no switch for its wheel smoothing but
applies a trackpad's small steps as they come, so each wheel event is
rewritten as a run of those in the shell's raw_input_hook;
- dropdowns, tooltips and dialogs appear without a fade, and scrolling to a
place jumps there (style.animation_time, style.scroll_animation);
- the rule along the top of a hovered card appears instead of wiping in.
Progress bars and spinners keep moving: they say that work is going on.
Where the setting comes from:
- Windows: SPI_GETCLIENTAREAANIMATION, read as bit 33 of UserPreferencesMask in
the registry, as SystemParametersInfoW needs unsafe;
- macOS: NSWorkspace's accessibilityDisplayShouldReduceMotion;
- Linux and the BSDs: the XDG settings portal over D-Bus (its reduced-motion,
GNOME's enable-animations, KDE's AnimationDurationFactor), followed on a
thread so the UI never waits for D-Bus;
- web: prefers-reduced-motion.
Animations stay on wherever the setting can't be read. winreg and zbus become
direct dependencies of the desktop app; both were already in Cargo.lock.
The Windows path was run on a machine with the setting off. The macOS and Linux
paths were type-checked for those targets but never run, and the web one only
compiled.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* UI tests: hovering a card follows the OS's animation setting
With animations off, moving the pointer onto a card leaves the window still
within 3 frames: its rule and its icon's rise show at once. With them on, the
0.3 s wipe takes 19 frames at 60 frames a second. Each test fails when the
card's timing is made to ignore the setting.
The test window no longer has a download in progress, so it is still unless
something animates.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(pl): use Polish plural rule, so 21 is "21 aplikacji"
plural_east_slavic treats 21, 31 and 101 as "one", which is right for
Russian but not Polish, where only 1 itself takes the singular.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* system theme: back off to every 5 minutes when there's no appearance portal
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Merge #189 and main into #201: one raw_input_hook, and test anchors on taglines
Main no longer shows the "Like …" product lines, so the tests find cards by their taglines.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(cs): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(pl): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(hu): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(ja): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: hide/unhide strings for the Spanish, Czech, Polish, Hungarian and Japanese catalogs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* hungarian test: count only the settings body's links, not the cards' Website links behind the modal
#198 gave every card a Website link, and Hungarian labels both Webhely.
* hungarian test: card links are 14px text, settings icons 24px; filter on that
---------
Co-authored-by: DzikStar <[email protected]>
Co-authored-by: ferivoq <[email protected]>
Co-authored-by: Losminek <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: leonaci <[email protected]>
Co-authored-by: satchmo91 <[email protected]>
Co-authored-by: Vikram Bhaskaran <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Add a Polish interface
- pl.tsv: the full catalog (287 rows), written clean-room from the English
labels; plurals use the same one|few|many rule as Russian.
- Registered as the seventh language: "Polski" in the picker, locales pl,
pl-PL and pl_PL.UTF-8, complete_menus = true so the completeness tests
cover it.
- tests/polish.rs: the Polish UI test, modeled on the French one.
- README and AGENTS updated for the new language.
* Add Hungarian localization and fix responsive settings layout
* i18n: add Czech (cs) interface language
Czech catalog with every key of the most complete catalog (287 rows), the
cs language entry with locale detection (cs-CZ, cs_CZ.UTF-8, ?lang=cs),
a Czech plural rule (1 / 2-4 / other) with a unit test, and the README
language list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: add Japanese (ja) interface language
ja.tsv translates the whole interface, the tray menu and tooltip, each app's
tagline and the backend's install/update messages into Japanese, and
Settings > Language offers 日本語. Japanese locales (ja, ja-JP, ja_JP.UTF-8…)
select it; Japanese has one plural form.
Japanese is a CJK language, so cjk_font.rs now looks for a Japanese system
font (Hiragino Sans on macOS; Yu Gothic, Meiryo or MS Gothic on Windows;
Noto Sans CJK JP, Noto Sans JP, IPAGothic, Takao or VL Gothic on Linux and
FreeBSD), falling back to a Chinese one, which draws kana and most kanji,
where the system has none. The font is loaded per language, so switching
between Japanese and Chinese reloads the right face.
A fallback face is nudged onto the body font's baseline: egui puts a fallback
glyph's baseline at that face's own ascent and only centres the two line
boxes, which left a face whose metrics differ from Inter's riding high. On
Windows, Yu Gothic's line gap is half an em, so its kana and kanji sat about
0.29 em above the Latin text beside them (Microsoft YaHei, used for Chinese,
happens to match). The nudge, applied through a FontTweak, is the difference
in ascent and line height, and helps the Chinese faces too.
The README and AGENTS list the new language. Tests: the complete-catalog
checks cover ja.tsv (placeholders, plural forms, every tl! literal, taglines,
package formats), plus a locale/plural test, a Japanese glyph-coverage test,
a baseline test and tests/japanese.rs (grid, table, dates, a backend message).
* i18n: make the CJK font choice language-neutral
The CJK comments, the missing-font note and the plural-rule docs named the
languages, so each new one meant editing them all; they now say "CJK". Each
language's writing system (its `script`) now lives in the registry, and a
language is drawn only with a face for its own script — never another's,
which would leave characters as tofu — so a language the system has no font
for stays in English, like any other. Adding Korean or Traditional Chinese is
a registry row and a candidate list, not a special case.
* Hide Craft apps the user is not interested in.
* i18n(pl): use Polish plural rule, so 21 is "21 aplikacji"
plural_east_slavic treats 21, 31 and 101 as "one", which is right for
Russian but not Polish, where only 1 itself takes the singular.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(cs): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(pl): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(hu): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n(ja): translate "Installed · latest {latest}" (added by #195)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* i18n: hide/unhide strings for the Spanish, Czech, Polish, Hungarian and Japanese catalogs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* hungarian test: count only the settings body's links, not the cards' Website links behind the modal
#198 gave every card a Website link, and Hungarian labels both Webhely.
* hungarian test: card links are 14px text, settings icons 24px; filter on that
---------
Co-authored-by: DzikStar <[email protected]>
Co-authored-by: ferivoq <[email protected]>
Co-authored-by: Losminek <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: leonaci <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>