Conversation
Adds a complete fr translation for both the main app and the FinderSync extension, registers fr in knownRegions, and lists French in the README localization table. - RClick/Localizable.xcstrings: 167 fr strings - FinderSyncExt/Localizable.xcstrings: 149 fr strings - scripts/check-localization.py --languages fr passes (every translatable key rendered, format specifiers preserved in order) Terminology follows Apple's French macOS glossary (Réglages Système, Accès complet au disque, Départ for the home folder, Ouvrir à la connexion). English stays the source language and fallback. No logic, signing, entitlement or bundle-id changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
feat(i18n): add French (fr) localization
- Introduce Shared/CustomMenu.swift with declarative tree node schema (item, submenu, separator) - Enable mixed top-level items and nested submenus with recursive NSMenu builder in FinderSyncExt - Support human-friendly references (app paths, extensions, action IDs) - Graceful fallback to default categorized menu if custom_menu.json is absent or invalid - Fix template ID and custom path loss across save/reload in ConfigService - Add robust fallback for opening accessibility system settings on macOS 15+ - Add comprehensive test suite in CustomMenuTests (three-level nesting, leaf dispatch, invalid layouts)
…and AI tip - Add 'Advanced Menu Layout' section in GeneralSettingsTabView with 'Open Config' and 'Reveal in Finder' buttons - Automatically generate initial custom_menu.json based on active apps/actions if missing, without overwriting existing files - Add friendly footer tip guiding users to leverage AI assistants (ChatGPT/Claude) for easy menu customization - Complete multilingual localizations in English, Chinese, French, Spanish, and Japanese - Add unit tests in CustomMenuTests covering safe initialization and non-overwrite semantics
feat(menu): support declarative hierarchical tree menu via JSON
# Conflicts: # RClick/Shared/PermissionChecker.swift
Xcode rewrote RClick/Localizable.xcstrings during a build:
- switch to Xcode's JSON style ("key" : "value")
- re-sort keys
- re-extract an orphan key: %@ -> v%@
No translation changes: 180 keys before and after, zero value
differences, identical per-language counts (en 176 / es 176 /
fr 177 / ja 169 / zh-Hans 177). Both the removed and the added
key were empty entries with no translations, so nothing
user-visible is affected.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- MARKETING_VERSION 2.2.0 -> 2.2.1 - CURRENT_PROJECT_VERSION 20260823001 -> 20260913001 Applies to the RClick and FinderSyncExt targets (Debug + Release), keeping the app and its embedded extension in sync. The build number follows the repo convention of YYYYMMDD plus a daily sequence. Co-Authored-By: Claude Code <noreply@anthropic.com>
CustomMenuError now conforms to LocalizedError. Without it error.localizedDescription degraded to the generic "The operation couldn't be completed." message, so the settings alert could never show the real validation reason. resolved(using:) gains an optional 1-based path, e.g. "3" or "2.1", wrapped around the existing body so the ~12 throw sites stay untouched. Children already carry their full path, so only the innermost error is wrapped. Adds an in-memory decode(_:config:) so callers holding the bytes do not read the file twice. No behaviour change for existing callers: the new parameter is defaulted, and the existing tests only assert that invalid layouts throw, not the message. Co-Authored-By: Claude Code <noreply@anthropic.com>
lastMenuSnapshot / lastSnapshot had no readers anywhere in the repo; the "for diagnostics / fallback" comment described something that was never implemented. Removing it also drops a try? that silently swallowed encoding failures. Co-Authored-By: Claude Code <noreply@anthropic.com>
The custom menu was opt-in and silent: an invalid custom_menu.json reverted to the default layout with nothing but a console log, and the settings alert could not report anything useful because CustomMenuError did not conform to LocalizedError. Advanced Menu Layout now shows the current state (not enabled / enabled / configuration invalid) and, when invalid, the specific offending entry, e.g. "[2.1] app reference ... matched 0 enabled items". Two buttons are added: - Apply Custom Menu re-reads and applies immediately instead of waiting for the extension heartbeat. It deliberately drives the same .menuConfigShouldUpdate path the heartbeat does, so pressing it and waiting 10 seconds must produce identical menus. - Restore Default Layout deletes the file and keeps a copy as custom_menu.backup.json, making the action reversible. The status is derived on demand rather than cached in buildConfig: that method only runs when the extension asks for a config, which never happens while the extension is disabled - exactly when someone opens Settings to investigate. Each button carries a tooltip stating whether it modifies the file, because "Open Config" also creates the file the first time. Error messages now point at the offending entry. MenuNode paths are attached by wrapping resolved(using:) so the existing throw sites stay untouched, and DecodingError coding paths are rendered as 1-based item paths ([2.1]) using key.intValue to find array indices. CocoaError keeps the system's own localized sentence, which is more accurate than anything hand-written here. Docs: examples/README.md documents the four buttons and the two equivalent ways changes take effect, and warns that the strings in the screenshots are author-written title overrides rather than built-in text. Co-Authored-By: Claude Code <noreply@anthropic.com>
… stray separator Three problems reported after testing the new controls. Clicking "Open Config" with no file present generated a config and silently switched the whole menu to the custom layout. A tooltip was not enough for an action with an invisible side effect, so generating now asks first and spells out that the right-click menu will change. An existing file still opens without a prompt. The seed inserted a separator between the flat items and "More". Because "More" is the last item and already reads as a submenu, the line grouped nothing and looked like a glitch, so it is gone. The existing seed test asserted that separator; it now asserts the new shape. "Common Dirs" existed only in the FinderSyncExt catalog, so seeding wrote the raw English key into custom_menu.json while its siblings were translated. The RClick catalog now carries the same five translations the extension already had. "Collapse menu" was the only one of the four collapse switches without a category in its label. Renamed to "Collapse common folders menu" to match its siblings. Also covers the common folders trap with a test: "Enable common folders" is not a pure layout switch, since MenuService only fills payload.commonDirs when it is on. Turning it off makes every common-dir reference match nothing and takes the whole custom layout down, not just those entries. examples/README.md now warns about it. Co-Authored-By: Claude Code <noreply@anthropic.com>
The main app re-read custom_menu.json on every extension heartbeat, so edits made in an external editor went live within ten seconds. That cannot tell "finished editing" from "half way through typing": a half-written file took the menu down, silently, mid-keystroke. The file is now a draft. MenuService keeps the bytes that are actually in effect and buildConfig renders from that snapshot, so nothing on disk moves the menu until "Apply Custom Menu" re-reads and validates it. A rejected edit throws and leaves the live menu exactly as it was rather than falling back to the default layout behind the user's back. Deleting the file stays the one exception - unambiguous and non-transient - and still returns to the default layout right away. CustomMenuStatus grows a second axis to say so: one value for what the menu is rendering, another for how the file relates to it. Without it "edited since it was applied" and "broken" collapsed into one state, and the case worth explaining - a settings change that invalidates an applied file, which is how turning off common folders takes the whole layout down - had nowhere to live. The settings screen drops from five elements to two. "Open Config" and "Reveal in Finder" merge into "Edit Menu Config...", which locates the file rather than opening it with whatever editor the system happens to have; its old tooltip claimed it changed nothing while it seeded the file and switched the layout. Apply Config greys out once the file matches what is in effect, so the grey is the receipt. The first-run confirmation alert is gone: seeding no longer switches anything, so the side effect it warned about no longer exists. MenuService now takes an injectable customMenuURL. buildConfig resolves the real App Group container, which is how the most important new test - an edit stays invisible until applied - was impossible to write. AppLocalization is nonisolated. Default main-actor isolation made it unreachable from LocalizedError.errorDescription, which has to stay nonisolated. Twelve string-catalog keys the old screen used are deleted. The catalog was edited by exact-line removal, not by re-serialising it. Co-Authored-By: Claude Code <noreply@anthropic.com>
The launch-at-login switch is the one people come to this page for, so it now sits above the menu bar icon toggle. The folder permissions row used folder.badge.person.crop, which reads as "shared with people"; the row is about granting RClick access to folders, so folder.badge.gearshape fits. Co-Authored-By: Claude Code <noreply@anthropic.com>
The five menu-layout switches lived in UserDefaults.standard while the menu bar toggle lived in the app group, so the same page read from two stores and two of its toggles disagreed about where they were saved. Swapping the store alone would have silently dropped every user's setting, because the old values become invisible and each switch falls back to its declaration default. "Enable common folders" is the one that bites: an empty payload.commonDirs makes any custom menu referencing a common folder stop resolving, which reverts the whole custom layout. Hence the one-time migration, which takes both stores explicitly so a test can drive it with throwaway suites instead of the real ones. Also drops a second, unread showMenuBar declaration whose store disagreed with the live one. Co-Authored-By: Claude Code <noreply@anthropic.com>
…resh Adding or deleting an app sometimes left the Finder menu unchanged, and a save that failed looked exactly like one that succeeded. Both had the same root cause: every mutation persisted and notified by hand, and the call sites disagreed about both halves. Most threw the error away, and adding an app, adding a file type, resetting either list and syncing common folders skipped the notification entirely, so the menu stayed stale until the next heartbeat. Collapsing both halves into a single persist() means a mutation cannot get one without the other. That closes the two reported bugs and four more of the same class structurally, rather than by patching each site. Failures were also invisible because they were logged at .info, which never reaches the log store, so the only symptom was a setting that quietly was not kept. They now land in lastSaveError, which the settings window surfaces as an alert, and are logged at .error. Co-Authored-By: Claude Code <noreply@anthropic.com>
Four gaps, found by auditing both string catalogs against the code:
1. The extension's "Open With" submenu had no Japanese, so Japanese users
saw the English title. The extension catalog was 148 keys, of which only
12 are ever rendered, so the gap was invisible. It is now 17.
2. AirDrop was untranslated in Simplified Chinese; it is 隔空投送 in Apple's
terminology. Japanese correctly keeps "AirDrop".
3. The "New File" submenu was untranslated in every language. NewFile was the
only model without a displayName, unlike RCAction and CommonDir, so the
built-in names could never resolve. A translation for PPTX had been
sitting unused in the catalog, which shows this was intended.
Its `all` literals also omitted `id`, so they took the init default of
UUID(). "Restore Defaults" therefore wrote random ids into the database
while the seeded rows use "txt"/"pptx", which breaks the id lookup the new
displayName depends on. Both now match createDefaultFileTypes().
JSON/TXT/Markdown/Pages/Keynote/Numbers deliberately stay out of the
catalog: they are format acronyms and Apple product names that no language
translates, and a missing key falls back to the key itself.
4. 20 keys that nothing references were deleted, most of them leftovers from
a permission-onboarding UI that has since been rewritten, and 130 more
that the extension catalog had inherited as a copy of the main app's.
"v%@" is deliberately kept despite looking unreferenced: Xcode extracts it
from Text("v\(version)") in SettingsView, where the literal never appears.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Xcode rewrote the catalog in its own key order while building, which turned every later catalog edit into a 1500-line diff and made review impossible. Isolating the reorder here keeps it out of the feature commits. No key was added, removed or changed: the key set and every key's content are byte-for- byte identical to the previous commit, only their order in the file differs. Co-Authored-By: Claude Code <noreply@anthropic.com>
The tab was four unrelated Sections: a centred logo block, a paragraph, a bare button, and a URL. The paragraph and the button each sat alone in their own box, which is why they read as disconnected and jarring. The button was worse than ugly. checkForUpdates wrote "already up to date" into updateError, and the sheet tests updateError before its else branch, so a user who was simply current saw a yellow warning headed "Failed to Check for Updates" — every time, since this button is the app's only check trigger. The friendly green "Up to Date" card was unreachable dead code. Underneath, GitHubReleaseChecker returned an optional that conflated four outcomes, and its catch swallowed network errors into a print. That made an offline check indistinguishable from being current. Returning an explicit result instead means the sheet's existing else branch is now reached with no change to the sheet at all, and a network failure finally reports itself. With the state honest, the button becomes the action half of a status row in the shape General already uses: label and state on the left, action on the right, so it always has the context it was missing. States: not checked yet, checking, up to date, update available (with a details button), failed. The identity block now holds the logo, name, version and description in one centred VStack, and the lone URL is replaced by two rows that give people something to do: the repository with its live star count and an "Add a Star" action, and a link to the issue tracker. The count is fetched from the GitHub API and cached for a day in the app group; if it cannot be fetched the row just loses a number rather than showing an error. Also drops the now-unreferenced github.com/wflixu/RClick key, plus four unused imports, an unused messager and a stale file header, and fixes the preview, which crashed for want of an environmentObject. Co-Authored-By: Claude Code <noreply@anthropic.com>
…ks for
The key was an empty object, so the check reported "no es translation" and
exited 1. It comes from Text("v\(version)") in the sidebar: the "v" prefix is
the same in every language, so each entry is just "v%@", but the key has to
be present for the check to pass.
Also drops the blank line inside the empty object.
Co-Authored-By: Claude Code <noreply@anthropic.com>
MARKETING_VERSION stays at 2.2.1: it was set back in ea2d4b2 but never tagged, so this release is 2.2.1 and only the build number moves. The build number follows the repo convention of YYYYMMDD plus a daily sequence, and applies to the RClick and FinderSyncExt targets (Debug and Release) so the app and its embedded extension stay in sync. The changelog had no entry for 2.2.0 or 2.2.1; this writes 2.2.1, which covers everything merged since the v2.2.0 tag. Co-Authored-By: Claude Code <noreply@anthropic.com>
Nine documents described work that has since shipped, and two of them said so themselves: 重构设计.md was marked "⚠️ 方向已过时" and 架构重构设计及计划.md was marked "已拆分" (split, i.e. a stub pointing at its own replacements). The three contracts under specs/contracts/ had drifted out of date: communication-protocol.md never mentions the HMAC signing that MessageSecurity now performs, and data-models.md predates BookmarkEntity and DataVersion. Both would mislead a reader more than they help, and CLAUDE.md already describes the protocol and the data models accurately. Checked before deleting: every reference to these nine was from another one of the nine, so nothing in the code, README, CLAUDE.md or examples/ is left pointing at a file that no longer exists. Kept, because they still describe how the project works today: specs/CI-CD与自动更新方案.md — the tag-driven release pipeline specs/design-bookmark-permission.md — the permission model specs/external-volumes-observation.md — the volume observation fix specs/contracts/git-branch-strategy.md — referenced by CLAUDE.md Co-Authored-By: Claude Code <noreply@anthropic.com>
The contributors section only carried the auto-generated graph, so the people behind the last few PRs were invisible. They are now listed with what they actually contributed and a link to each PR. CLAUDE.md still said only Chinese and Japanese were supported, two releases after Spanish and French shipped. It also never mentioned that there are two catalogs, that the extension resolves against its own, or that the catalogs must be edited as text rather than re-serialized — all three of which cost time to rediscover. README.md was English throughout except for one appended Chinese section, which is now English like the rest of the file. Moving it out from under "Contributing" is left alone as an editorial choice. Also fixes the PR template's link to CONTRIBUTING.md, which resolved relative to .github/ and so pointed at a file that does not exist. Co-Authored-By: Claude Code <noreply@anthropic.com>
CI failed on PR #153 with five "No such file or directory" errors naming specs that the previous commit deleted. The deleted files were members of the Copy Bundle Resources phase of all three targets, so removing them from disk left the project pointing at files that no longer existed. That the docs were bundle resources at all was the real defect: README.md, CLAUDE.md and every design spec were being copied into RClick.app/Contents/ Resources and into the extension bundle, three times over, and nothing reads them. CLAUDE.md in particular is internal development guidance that has no business in a shipped app. So instead of only dropping the seven stale references, this removes all ten documents from all three Resources phases, and drops the file references and group entries for the seven that no longer exist. The three that remain on disk (README.md, CLAUDE.md, git-branch-strategy.md) keep their file references so they stay visible in Xcode's navigator. Verified with the exact commands from pr-build.yml: build and test both pass, and neither RClick.app nor FinderSyncExt.appex contains a .md file any more. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Type of Change
Related Issue
Screenshots
Testing
Manual Testing
Build Verification
Checklist
devAdditional Notes