Commit 9ffe618
authored
Localization string actions delegating to bandlab-localizer (#215)
* Add localization string actions delegating to bandlab-localizer
The plugin never edits string resource files directly — every add/remove delegates to the
bandlab-localizer CLI, which owns merge, validation, key-sorting and multi-locale output. The
actions read the manifest and shell out, so they work even when Gradle sync fails.
- "Localizer" submenu (Tools menu, Project View popup, editor popup), shown whenever the project's
bandlab-localizer-config.toml resolves:
- Update Strings -> `update-strings` (full sync).
- Add Strings -> dialog (target-file picker + multi-key paste) -> `update-strings --add-keys
--add-keys-to-file`. Targeted; pre-selects the clicked file's `[[file]]` group.
- Delete Strings -> dialog -> `update-strings --delete-keys`.
- Alt+Enter "Delete String" intention on a `<string>`/`<plurals>` element (any caret position) ->
`update-strings --delete-keys` for that one string.
- Runs the CLI as a tracked process in a Console tab (using the IDE's captured shell env so the
wrapper finds java), then refreshes the manifest's files on completion so open editors reload.
- Reads the manifest via the published `com.bandlab.localizer.config:config` (3.2.0-SNAPSHOT for now).
Related to #214.
* Add editor panel and R.string "Add string" intention; share ops via LocalizerOps
- Editor banner (EditorNotificationProvider) on manifest string files, with Update / Add / Delete
Strings quick links.
- Alt+Enter "Localizer: Add string" intention on an `R.string.X` / `R.plurals.X` Kotlin reference
that isn't defined locally yet -> `update-strings --add-keys X`. PSI-only (no Gradle sync).
- Extract LocalizerOps so the menu actions, the panel, and the intentions share one CLI-invocation
+ file-refresh path instead of each building it.
* Add caret "Update String" intention via --update-keys
Alt+Enter on a `<string>`/`<plurals>` element in a manifest string file re-fetches that one string
fresh from Tolgee and overwrites it in place (base + every translation) via
`update-strings --update-keys`. Mirrors the Delete String intention; routes through LocalizerOps.
* Update Strings scope dialog + Update String on managed R.string refs
- "Update Strings" (menu/panel) now opens a dialog: All strings (full sync) or Selected strings
(`update-strings --update-keys <keys>`), so the scope is explicit instead of always full-sync.
- The "Update String" intention also fires on an `R.string.X` / `R.plurals.X` reference whose key
is already defined in a managed base file (re-fetch), mirroring the Add-string intention which
fires when the key is not yet local.
* Warn on hand-editing localizer-managed string files (banner → warning)
* Shorten Update/Delete intention labels (drop "String")
* Warn-on-typing for managed string files + drop editor-popup leak
- Editor: remove the Localizer submenu from the editor (text) popup — IntelliJ surfaces editor-popup
actions in the Alt+Enter list, so it leaked "Update Strings" into the intention bulb. The warning
banner + the intentions cover the editor; the submenu stays in Tools menu + Project View.
- New: a non-blocking warning when hand-editing a managed string file (TypedHandlerDelegate, once per
file per session) nudging toward the Localizer actions. Configurable under Settings > Tools >
Localizer (LocalizerSettings + LocalizerSettingsConfigurable), on by default.
- Disable buildSearchableOptions (collides with a running IDE; settings stay searchable without it).
* Add icons to Localizer Alt+Enter intentions
- Implement Iconable on the Update/Delete/Add string intentions
- Update ↻ (Actions.Refresh), Delete − (General.Remove), Add + (General.Add)
so the bulb-list entries are visually distinguishable
* Resolve aliased R.string references in Add/Update intentions
- Recognize R.string/R.plurals references reached via an import alias
(import com.app.R as appR) or a top-level typealias, not just bare
R.string.X / com.app.R.string.X. bandlab-android disambiguates its
several per-module R classes with import aliases, so the intentions
previously didn't fire on the common audiostretchCommonStringsR.string.X form.
- Resolve the receiver head (alias-aware) and check it stands for an R class;
keep the textual receiver check as a fallback for unsynced/unindexed code.
The key itself need not resolve, so the Add (unresolved-key) case still works.
- Code references only; the XML <string>/<plurals> path is untouched.
- Tests: import-alias, typealias, and typealias-to-non-R-class cases.
* Surface "Add string" as a red-section quick fix; prioritize Update/Delete intentions
- Add AddStringUnresolvedQuickFixRegistrar: contributes "Localizer: Add string"
to the red error-fix section on an unresolved R.string/R.plurals reference
(key not defined yet), via the Kotlin K2 UNRESOLVED_REFERENCE diagnostic
(org.jetbrains.kotlin.codeinsight.quickfix.registrar EP). Sits beside Android's
"Create string value resource" instead of in the yellow intention list.
Handles bare / FQN / import-aliased / typealiased receivers via shared resStringKey.
- The always-on AddStringFromReferenceIntention stays as a fallback until the
red-section path is confirmed in the IDE (transient duplicate in the unresolved case).
- Mark Update/Delete intentions HighPriorityAction so they float to the top of the
yellow list (they act on a known-existing string).
- Declare K2 plugin-mode support (the diagnostic API is FIR/K2-only) + depend on
the Kotlin plugin to register the EP.
* Fix managed-file banner label: "Add String" → "Add Strings"
Match its Update Strings / Delete Strings siblings (all open multi-key dialogs).
* Retire the duplicate always-on "Add string" intention
The red-section unresolved-reference quick fix now owns the Add case, so the
always-on yellow AddStringFromReferenceIntention is redundant. Move the shared
R.string detection helpers (resStringKeyAt / resStringKey / localBaseKeys) to
ResStringReference.kt; drop the intention class + its plugin.xml registration.
* Sort Update above Delete in the ⌥⏎ popup
Both fire on a <string> element in a managed string file; bump Update to
PriorityAction.Priority.TOP (Delete stays HIGH) so Update lists first.
* Resolve Add-Strings target by R class instead of defaulting to first file
- Map R class FQN -> module base path; an unresolved R.string/R.plurals quick fix adds straight to the single mapped target, or prompts when ambiguous/unmapped
- Force an explicit target pick in the Add dialog (no silent default), pre-filling the key
- Mark the "Add string" quick fix HighPriorityAction so it tops the Alt+Enter list
* Replace edit-warning balloon with a blocking, per-branch dialog
- First keystroke in a managed file is consumed and opens a blocking dialog (edit on this branch / Update / Add / Delete / cancel) instead of a passive balloon
- Remember "edit on this branch" in the project workspace keyed by git branch (.git/HEAD, worktree-aware); unknown branch falls back to ask-once-ever
- Drop the now-unused balloon notification group
* Keep managed-file banner actions out of the Alt+Enter popup
Override EditorNotificationPanel.getIntentionAction() to null so the banner Update/Add/Delete labels stay on the banner but no longer surface as suggested intentions.
* Vendor config.jar instead of resolving it from a Maven repository
- Bundle config.jar (built from localizer 3.2 code) under libs/, depended on via files()
- Declare config's runtime deps explicitly (app.gildor:ktoml-file, kotlinx-serialization-json) since file() deps carry no transitive metadata
* Simplify the edit-warning dialog to Edit/Cancel
- Drop the per-command buttons; the banner and Alt+Enter intentions already expose Update/Add/Delete, so the dialog just gates hand-editing
- No default-highlighted button; the message points to the context actions and editor toolbar
* Lead the Alt+Enter list with the Localizer actions
- The Add-string quick fix and the Update/Delete intentions use PriorityAction.TOP so they head their groups
- Register Update before Delete so they sort in that order
* Address Copilot review: offset guards, base-key scan, dispose guard
- Guard findElementAt(offset - 1) when the caret is at file start (resStringKeyAt, stringKeyAt)
- localBaseKeys: match name= anywhere in the opening tag (not only as the first attribute), and cache the scan (invalidated on PSI change) so a moving caret no longer re-reads every base file
- LocalizerRunner: skip the post-process VFS refresh when the project is already disposed
* Document the localization string actions in the README
Replace the old "Update Localized Strings" section (module-level full re-sync) with a concise Localization Strings section covering the new surfaces: Add on unresolved references, per-key Update/Delete in string files, global Add/Update/Delete, and the managed-file edit reminder.
* Removed stale comment
* Fix ⌥⏎ order: keep Update above Delete
Both intentions were PriorityAction.TOP, which ties and tie-breaks alphabetically — putting Delete above Update. Restore Delete to HIGH so Update (TOP) sorts first while both still lead the intentions group.
* Detect the Strings/Plurals member-alias reference form
The dominant bandlab-android form is `Strings.key` (typealias `Strings = R.string`, `Plurals = R.plurals`), where the alias lives beside `R` in the same strings module — previously undetected, so "Add string" never appeared.
- Recognize a plain-name receiver `Strings`/`Plurals` as a string/plurals reference; map `<pkg>.Strings` → `<pkg>.R` by reading the import (text only, no type resolution — works without a Gradle sync). A matching import is required, so locals named `Strings` do not false-positive.
- Add tests for the member-alias forms and a guard that Update outranks Delete in the ⌥⏎ popup.
* Gate Strings/Plurals detection to known strings packages
A bare-name receiver like `Strings` is ambiguous, so recognizing it as a string reference unconditionally would offer "Add string" on any unrelated class named `Strings`. Gate it on the known strings-module packages — only `<pkg>.Strings`/`<pkg>.Plurals` whose `<pkg>.R` is in the hardcoded map count.
- Lift R_CLASS_TO_MODULE to a shared internal constant + isKnownRClass(), reused by both target resolution and reference detection (single source of truth).
- Explicit `R.string.X` forms stay ungated (syntactically unambiguous); only the bare-name form is gated.
* Drop the Localizer settings; always remind on a new branch
Remove the application settings + Tools > Localizer configurable and the warn-on-edit toggle. The per-branch "edit on this branch" memory is the real control; reminding on every fresh branch is intentional — it builds the habit of going through the actions. A toggle can come back if anyone actually asks.
* Group Update + Delete at the top of the ⌥⏎ list
Both intentions are PriorityAction.TOP so they sit together at the top rather than being split by another TOP-priority action (Android's "Open editor"). IntelliJ only orders by priority tier then alphabetically — no custom ordinal — so the visible order is Delete then Update, accepted as the trade for keeping them adjacent.
* Add localization-actions screenshots to the README
Replace the stale terminal screenshot with shots of the main areas: add/update from a reference, the managed-file banner + context actions, the global actions, and the edit-warning dialog. Committed under docs/images and referenced by relative path (renders on GitHub; swap to absolute URLs if the in-IDE/Marketplace description needs them).
* Adopt localizer 3.2.0 release: ktoml 0.8.1
- Bump app.gildor:ktoml-file 0.8.0 → 0.8.1 (matches localizer 3.2.0)
- Vendored libs/config.jar verified byte-identical to the published
config 3.2.0 release artifact (libs-release), so no jar change needed
* Declare config's runtime deps in the version catalog
Move app.gildor:ktoml-file and kotlinx-serialization-json from inline coordinates into gradle/libs.versions.toml, per review.1 parent 23f09a5 commit 9ffe618
41 files changed
Lines changed: 1823 additions & 225 deletions
File tree
- docs/images
- gradle
- libs
- src
- main
- kotlin/com/bandlab/intellij/plugin
- localizer
- strings
- resources
- META-INF
- intentionDescriptions
- AddStringFromReferenceIntention
- DeleteStringIntention
- UpdateStringIntention
- test/kotlin/com/bandlab/intellij/plugin
- localizer
- strings
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
128 | | - | |
| 127 | + | |
129 | 128 | | |
130 | | - | |
131 | | - | |
132 | | - | |
| 129 | + | |
133 | 130 | | |
134 | | - | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
135 | 150 | | |
136 | 151 | | |
137 | 152 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| |||
148 | 154 | | |
149 | 155 | | |
150 | 156 | | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
151 | 163 | | |
152 | 164 | | |
153 | 165 | | |
| |||
Loading
Loading
Loading
Loading
Loading
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
12 | 16 | | |
13 | 17 | | |
14 | 18 | | |
| |||
27 | 31 | | |
28 | 32 | | |
29 | 33 | | |
| 34 | + | |
| 35 | + | |
30 | 36 | | |
31 | 37 | | |
32 | 38 | | |
| |||
Binary file not shown.
Lines changed: 51 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
0 commit comments