diff options
| -rw-r--r-- | AGENTS.md | 51 | ||||
| -rw-r--r-- | CHANGELOG.md | 15 | ||||
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md | 45 | ||||
| -rw-r--r-- | docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md | 65 | ||||
| -rw-r--r-- | docs/superpowers/specs/2026-10-31-rule-apply-and-archive-design.md | 76 | ||||
| -rw-r--r-- | translations/qtmaildir_it_IT.ts | 70 |
6 files changed, 258 insertions, 64 deletions
@@ -290,7 +290,7 @@ A test for this needs a thread whose messages DISAGREE about the tag, for the same reason item 87 records: two messages in the same state answer identically whichever way the code resolves them. -**There is exactly ONE exception, and its shape is the rule's own logic rather +**There are two exceptions, and their shape is the rule's own logic rather than a hole in it.** `empty_trash` (item 118) destroys files and index entries, so it has no inverse to push, and the protection the rule actually provides — that a user never loses work to a keystroke — has to come from somewhere else. @@ -298,7 +298,16 @@ It therefore asks, naming the count and the account, defaulting to Cancel, and it carries **no default shortcut** for the same reason. `NotmuchWorker::purgeMessages()` is a separate entry point from `moveMessages()` deliberately: the two look alike and only one of them can be undone. A second confirmation anywhere is a -defect unless the action is likewise irreversible. All actions funnel through one `applyTags` path; +defect unless the action is likewise irreversible, or is the second exception. + +The second is **a write scoped to the whole index rather than to rows on +screen**: applying a tagging rule to existing mail (item 212). It IS undoable, +through the same exact-undo path as every tag write, and it confirms anyway, +naming the count, because the user cannot see what it will touch. The two +exceptions have different reasons and must keep them: `empty_trash` confirms +because it cannot be undone, Apply because it cannot be seen. + +All actions funnel through one `applyTags` path; multi-row selections go through `applyTagsToThreads`, which resolves every thread in ONE combined `thread:a or thread:b` query rather than one query per thread. @@ -1311,7 +1320,8 @@ schema: **Two hook properties are safety-critical, and being ours now is not a reason to weaken them.** -The hook refuses to remove `unread` or `inbox` (`maildir.synchronize_flags` is +The hook refuses to remove `unread`, and `inbox` unless the rule sets +`allow_archive`, item 213 (`maildir.synchronize_flags` is true, so removing `unread` rewrites Maildir filenames and reaches the server), and it does not consume the `tag:new` marker when the rules fail to load (clearing it while rules did not run orphans that mail permanently and @@ -1319,6 +1329,35 @@ invisibly). A dialog here that offers to write such a rule would produce one the hook then refuses; that is the correct direction, but say so in the UI rather than letting it fail silently. -Backfill, applying a rule to existing mail, is deliberately unbuilt. See -`docs/superpowers/specs/2026-08-12-tagging-rules-design.md` for what it needs -first, including the revision it forces to the no-confirmation rule above. +Backfill exists since item 212 as the dialog's **Apply to existing mail**, and +it runs in qtmaildir, attended and confirmed, never in the hook. Three of its +properties are load-bearing, and each replaced a version that broke: + +- **The worker resolves the ids ONCE and writes one tag at a time over that + fixed list.** `MainWindow::confirmRuleApply()` sends + `NotmuchWorker::applyRule(query, add, remove, description)`, which resolves + `SearchTerm::changes(query, add, remove)` once. One tag per write, because + `applyTags()` reports which MESSAGES moved, not which tags: one `+promo + -inbox` write lists a message that already had `promo` (it lost `inbox`), and + its undo strips `promo` from it. Ids once, because re-resolving per operation + made a rule whose query names its own tags apply only half of itself. +- **The worker always answers with ONE `ruleApplied(description, changes, + error)`**, one `TagChange` per operation carrying that operation's effective + ids, and never emits `tagsApplied` or `errorOccurred`. `onRuleApplied()` + records each non-empty change through `recordConfirmedTagChange()` (the + bookkeeping of `onTagsApplied()` without crediting a queued command), pushes + `MessageTagCommand`s built with the known ids in one undo macro, marks every + account edited so the next sync covers it, and re-queries. A partial write + still gets its undo, and the error goes to the status bar. +- **Nothing is armed in advance.** An earlier version queued its commands on + `m_awaitingTagConfirmation` before the write; a user gesture during a slow + backfill cleared that queue, and the user's own command then took the + backfill's ids, so Ctrl+Z on an Archive re-inboxed thousands of messages. + Do not route a backfill through `awaitTagConfirmation()` again. + +Apply during a sync is REFUSED, checked before the count and again after the +confirmation box, whose nested event loop a sync can start inside: `HeldEdit` +replays ids, and a backfill has none until the worker resolves its query. The +box says the write can be undone "until the next search", because `runQuery()` +clears the undo stack. There is no Redo action; the backfill's commands carry +real ids, so they would redo correctly if one is added. diff --git a/CHANGELOG.md b/CHANGELOG.md index 736fca0..9e1d500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,14 @@ point at which they are stable. - A second launch now hands its selectors to the already-running window over a local socket and asks it to raise itself, rather than opening a second window. One process, and so one notmuch database handle. +- **Apply to existing mail** in the tagging rules dialog. It saves the + rules, counts the messages the selected rule would actually change, asks + with that count, and applies it. Undo reverts exactly what moved, until the + next search. It is refused while a sync is running. +- **Archive matching mail on arrival**, a per-rule checkbox. A rule that + removes `inbox` used to be skipped whole by the sync hook; with the box + ticked it runs, and `inbox` is removed. `unread` is never removed on + arrival. The dialog now names every rule the hook will skip. ### Changed @@ -50,6 +58,13 @@ point at which they are stable. - Clicking a conversation's first message in the overview's unread list now opens that message, instead of doing nothing visible. +### Upgrading + +- A rule that removes `inbox` was never applied by the sync hook. Open the + rules dialog: such rules are named in the warning. Tick "Archive matching + mail on arrival" on each one you want to run, then use "Apply to existing + mail" to catch up on what arrived meanwhile. + ## [0.30.0] - 2026-09-29 A calendar window over the vdirsyncer vdir, with a month grid, an agenda and diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md index c83e41c..376272b 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability-closed.md @@ -10912,3 +10912,48 @@ while moves report through another signal. An error bumps the same ticket, so a late "done" cannot cover the error message. Tested by `aWriteSaysItIsApplyingUntilTheWorkerHasRunIt`, which fails when the reply is dropped. + +## 212. A rule edit reaches only new mail + +**Observed.** From the user, 2026-10-31: after adding an address to the promo +rule, "I have to manually go and apply the tags, because filters run on new +mail only." + +**Cause.** Not a defect. The `post-new` hook scopes every rule to `tag:new`, +and backfill was deliberately deferred by the tagging-rules spec until the +confirmation question was settled. + +**Approach.** See `specs/2026-10-31-rule-apply-and-archive-design.md`, Part A. + +**Constraints.** Revises the no-confirmation rule in `CLAUDE.md` with a second +exception, by property: a write scoped to the whole mailbox confirms with the +count. Undo still applies. + +**Done 2026-10-31, unreleased.** Built as the spec's Part A with three changes +recorded there and in `CLAUDE.md`: the worker's `applyRule` resolves the ids +once and writes one tag at a time, it answers with one `ruleApplied` from which +`onRuleApplied` builds the undo macro (nothing is armed on the shared +confirmation queue in advance), and Apply during a sync is refused rather than +held. The ordering defect found on the way, in the ordinary undo path, is item +214. + +## 213. A rule removing `inbox` is skipped whole by the hook + +**Observed.** Found while designing 212. Every sync logs +`rule 'promo' would remove inbox; skipped`, so new promotional mail is never +tagged `promo` and stays in the inbox. This is how the 10k messages of item +211 accumulated. + +**Cause.** Verified in `assets/hooks/post-new`: `PROTECTED_REMOVALS` is +`{unread, inbox}` and a rule naming either is `continue`d past entirely, not +applied without the removal. The rules dialog says nothing about it. + +**Approach.** See the same spec, Part B. + +**Constraints.** A rule-format change: both readers, both suites, and the hand +round trip under "Changing the rule format". The hook runs every ten minutes +on real mail. + +**Done 2026-10-31, unreleased.** Built as the spec's Part B. The hand round +trip under "Changing the rule format" step 4 was run over a throwaway index; +its counts are in the commit that closed this item. diff --git a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md index 6e36905..200b6d0 100644 --- a/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md +++ b/docs/superpowers/plans/2026-08-03-post-0.1.0-usability.md @@ -149,8 +149,9 @@ taking that too literally. | 209 | The calendar has no Week view | presentation | M | open, from 206's brainstorm. The occurrence list is the seam: a new painter over CalendarItem, no store change | | 210 | A repeating event cannot be edited "this and following" | workflow | S-M | open, from 206's brainstorm. Splits a series into two files, divides EXDATEs and overrides, shifts moved RECURRENCE-IDs, converts COUNT; see the spec's Out of scope | | 211 | Archiving thousands of selected rows freezes the window | defect | S | **done** 2026-10-31, unreleased. Cubic optimistic-update path made linear: a lazily rebuilt id index in `ThreadListModel` and `MainWindow::batchModelWrites()` refreshing the action labels once per loop. A write now reads "applying…" until the worker has run it, then "done". Hand-tested on a 10158-message archive | -| 212 | A rule edit reaches only new mail; applying it to the backlog is a hand-typed `notmuch tag` | workflow | M | open, designed: `specs/2026-10-31-rule-apply-and-archive-design.md` Part A. Apply saves, counts what would change, confirms with the count (a new documented exception to the no-confirmation rule), writes through `applyTags` for exact undo | -| 213 | A rule removing `inbox` is skipped whole by the hook, so promo mail arrives untagged | defect | S-M | open, designed: same spec, Part B. Optional per-rule `allow_archive`, both readers, no version bump; `unread` stays refused; the dialog warns about rules the hook will skip | +| 212 | A rule edit reaches only new mail; applying it to the backlog is a hand-typed `notmuch tag` | workflow | M | **done** 2026-10-31, unreleased. The rules dialog's Apply to existing mail saves, counts, confirms with the count (the second documented exception to the no-confirmation rule) and writes through `NotmuchWorker::applyRule`, one tag at a time over ids resolved once, with one undo macro built from the worker's answer. Refused during a sync. Section in the closed file | +| 213 | A rule removing `inbox` is skipped whole by the hook, so promo mail arrives untagged | defect | S-M | **done** 2026-10-31, unreleased. Optional per-rule `allow_archive` in both readers, no version bump; the hook lets an opted-in rule remove `inbox`, never `unread`; the dialog names every rule the hook will skip. Section in the closed file | +| 214 | Undoing a tag change while another action is still landing can give the undo's messages to that action | defect | S? | open, 2026-10-31, found by review during 212; pre-existing. An undo's write is not armed, so its confirmation is credited to whatever command is waiting. A correlation token on `TagChange` is the robust fix | | 73 | This backlog is past four thousand lines | maintenance | S | **done** 2026-08-13; 5056 lines to 578, closed sections moved to `2026-08-03-post-0.1.0-usability-closed.md` | | 74 | "Searching..." keeps claiming a query is running while rows are already arriving | feedback | XS | **done** 2026-08-15, unreleased. The status-bar half only: the bar now counts threads per batch. The cold-cache delay itself was measured in 2026-08-11 and is not fixable here | | 75 | The tagging rules window forgets its size and its column widths | persistence | S | **done** 2026-08-13, shipped in 0.17.0. The window-kind question is left open, see the closed-items file | @@ -1701,35 +1702,31 @@ server-normalised source must refuse rather than divide a file it does not recognise. See the spec's Out of scope for the full list of what the split has to divide. -## 212. A rule edit reaches only new mail - -**Observed.** From the user, 2026-10-31: after adding an address to the promo -rule, "I have to manually go and apply the tags, because filters run on new -mail only." - -**Cause.** Not a defect. The `post-new` hook scopes every rule to `tag:new`, -and backfill was deliberately deferred by the tagging-rules spec until the -confirmation question was settled. - -**Approach.** See `specs/2026-10-31-rule-apply-and-archive-design.md`, Part A. - -**Constraints.** Revises the no-confirmation rule in `CLAUDE.md` with a second -exception, by property: a write scoped to the whole mailbox confirms with the -count. Undo still applies. - -## 213. A rule removing `inbox` is skipped whole by the hook - -**Observed.** Found while designing 212. Every sync logs -`rule 'promo' would remove inbox; skipped`, so new promotional mail is never -tagged `promo` and stays in the inbox. This is how the 10k messages of item -211 accumulated. - -**Cause.** Verified in `assets/hooks/post-new`: `PROTECTED_REMOVALS` is -`{unread, inbox}` and a rule naming either is `continue`d past entirely, not -applied without the removal. The rules dialog says nothing about it. - -**Approach.** See the same spec, Part B. - -**Constraints.** A rule-format change: both readers, both suites, and the hand -round trip under "Changing the rule format". The hook runs every ten minutes -on real mail. +## 214. An undo's write is not armed, so its confirmation is credited to whatever command is waiting + +**Observed.** Not a user symptom yet: found by review while building item 212, +and pre-existing. The 212 backfill hit the same mechanism from the other side +(a user gesture during a slow backfill let the user's command take the +backfill's ids, so Ctrl+Z on Archive re-inboxed thousands), which is why it was +redesigned to arm nothing. + +**Cause.** Verified in `src/mainwindow.h`: `MessageTagCommand::undo()` and +`ThreadTagCommand::undo()` call `sendMessageTagChange()` / +`sendThreadTagChange()` WITHOUT `awaitTagConfirmation()`, while `redo()` arms. +`MainWindow::onTagsApplied()` credits every confirmation to the head of +`m_awaitingTagConfirmation`. So if a user gesture's command is still waiting +when an undo's confirmation arrives, that command takes the undo's ids, and its +own later undo acts on messages it never moved. That is item 176's data-safety +property failing by ordering rather than by scope. + +**Approach.** Unsized, probably S. The robust fix is a correlation token on +`TagChange`, set by the sender, echoed by the worker in `tagsApplied`, and used +by `onTagsApplied()` to find the command it belongs to, so the queue's order +stops mattering. Arming the undo's write as well would close this case and +leave the queue's FIFO assumption in place for the next one. + +**Constraints.** Item 176's rule: an undo covers what the write changed, never +what it asked for. A test needs a gesture's command waiting while an undo's +confirmation arrives, with the two touching DIFFERENT messages, or the wrong +credit answers identically. `a30b4c2` tried crediting by matching tags and was +reverted (`aa5fc73`); read both before choosing. diff --git a/docs/superpowers/specs/2026-10-31-rule-apply-and-archive-design.md b/docs/superpowers/specs/2026-10-31-rule-apply-and-archive-design.md index 247cfd5..380c2e3 100644 --- a/docs/superpowers/specs/2026-10-31-rule-apply-and-archive-design.md +++ b/docs/superpowers/specs/2026-10-31-rule-apply-and-archive-design.md @@ -73,26 +73,54 @@ Then: > Apply rule "promo" to 1,234 messages? > Adds: promo. Removes: inbox. - > You can undo this from the main window. + > You can undo this until the next search. + + (Changed during implementation from "from the main window": `runQuery()` + clears the undo stack, so the undo lasts until the next search.) Cancel is the default button. The count and the tag lists are the information the user cannot get from the list on screen, which is the whole reason this write gets a confirmation and others do not. -4. **Write.** A new worker slot, `applyTagsToQuery(query, add, remove, - description)`, runs the same change query to collect message ids, then - calls the existing `applyTags()`. Everything downstream is inherited - unchanged: - - exact undo: `applyTags()` reports only the ids whose tags moved (item - 176), and the undo entry is a `MessageTagCommand` armed with - `awaitTagConfirmation()` before the send, so Ctrl+Z reverts exactly - those; - - the pending-changes count and the auto sync, from `onTagsApplied()`; - - the "applying… / done" status from item 211's `announceAction()`; - - the write lock: if a sync holds it, the edit is held and replayed the - same way a list edit is. - - After the confirmation arrives, the current query is refreshed, since the +4. **Write.** A new worker slot, `applyRule(query, add, remove, + description)`, resolves the message ids ONCE with + `SearchTerm::changes(query, add, remove)`, then writes one tag operation + at a time over THAT FIXED id list, and always answers with one + `ruleApplied(description, changes, error)` carrying each operation's + effective ids. It never emits `tagsApplied` or `errorOccurred`. + (Changed during implementation; the planned `applyTagsToQuery` calling + `applyTags()` once is gone, for the three reasons below.) + + **One tag at a time.** `applyTags()` reports which messages moved, not + which tags, so one `+promo -inbox` write would list a message that + already had `promo`, and undo would strip it. Each tag is its own write + and its own undo command, grouped in one undo macro. + + **Ids resolved once.** Re-resolving per operation made a rule whose query + names its own tags apply only half of itself. + + **Nothing armed in advance.** The planned `MessageTagCommand` armed with + `awaitTagConfirmation()` before the send was built and then removed: a + user gesture during a slow backfill cleared the confirmation queue, and + the user's own command took the backfill's ids (Ctrl+Z on Archive + re-inboxed thousands). `MainWindow::onRuleApplied()` instead records each + non-empty change through `recordConfirmedTagChange()`, which is the + bookkeeping of `onTagsApplied()` without crediting a queued command, and + pushes `MessageTagCommand`s built with the known ids in one undo macro. + + What is inherited: + - exact undo: each command holds the ids its operation moved (item 176); + - the pending-changes count, from `recordConfirmedTagChange()`, and every + account marked edited, since a backfill reaches mail no row shows and + its accounts cannot be resolved through the model; + - the "applying… / done" status from item 211's `announceAction()`; a + failure goes to the status bar, and a partial write still gets its undo; + - the write lock: Apply during a sync is REFUSED with a status message, + checked before the count and again after the confirmation box. + Changed during planning: `HeldEdit` replays message or thread ids, and + a backfill has neither until the worker resolves its query. + + After the answer arrives, the current query is refreshed, since the rows a backfill touches are mostly not in the model and cannot be updated optimistically. @@ -101,16 +129,16 @@ Then: anything when cron runs a sync"; Apply has a human present who has just confirmed a named count. -### The undo command needs its ids from the confirmation +### The undo command's ids come from the worker's answer `MessageTagCommand` is constructed with the ids it acts on. For a backfill -those ids are unknown until the worker has resolved the query. The command is -constructed with an empty id list and filled from the confirmation, which -`TagCommand::addChangedMessageIds()` already does for every command (item -176). The plan must verify that redo and undo both read the EFFECTIVE set and -never the constructor's list; if either reads the constructor's list, an -empty one would redo nothing, and the plan fixes that rather than working -around it. +those ids are unknown until the worker has resolved the query, so the +commands are built only when `ruleApplied` arrives, with the effective ids +each operation reported, and `addChangedMessageIds()` fills what undo reads. +(Changed during implementation from "constructed with an empty id list and +filled from the confirmation", which needed the arming removed above.) The +application has no Redo action; since the commands carry real ids, they +would redo correctly if one is added. ### Rule change to CLAUDE.md @@ -184,7 +212,7 @@ Measurable properties only, per `CLAUDE.md`. - **SearchTerm:** the change query for `+a -r` over a query containing ` or ` is the exact expected string, with both sides parenthesised. Add-only and remove-only rules each produce the right half. -- **Worker:** `applyTagsToQuery` against a throwaway index holding messages in +- **Worker:** `applyRule` against a throwaway index holding messages in BOTH states (some already tagged, some not; the item 87 lesson) reports only the ones that changed, and leaves the rest alone. - **MainWindow, worker-backed:** Apply on a rule with N changeable messages diff --git a/translations/qtmaildir_it_IT.ts b/translations/qtmaildir_it_IT.ts index 7a57598..7916ec1 100644 --- a/translations/qtmaildir_it_IT.ts +++ b/translations/qtmaildir_it_IT.ts @@ -1683,6 +1683,53 @@ protocollo di rete: il recupero e l'invio sono comandi esterni. <translation><b>%1</b> messaggi in <b>%2</b> conversazioni<br><b>%3</b> etichette</translation> </message> <message> + <source>A sync is running; apply the rule again when it finishes.</source> + <translation>Sincronizzazione in corso; applica di nuovo la regola al termine.</translation> + </message> + <message> + <source>Rule "%1" could not be counted, so nothing was applied.</source> + <translation>Impossibile contare la regola "%1", quindi non è stato applicato nulla.</translation> + </message> + <message> + <source>Rule "%1" has nothing to change.</source> + <translation>La regola "%1" non ha nulla da cambiare.</translation> + </message> + <message> + <source>Adds: %1.</source> + <translation>Aggiunge: %1.</translation> + </message> + <message> + <source>Removes: %1.</source> + <translation>Rimuove: %1.</translation> + </message> + <message> + <source>Apply rule</source> + <translation>Applica regola</translation> + </message> + <message numerus="yes"> + <source>Apply rule "%1" to %n message(s)?</source> + <translation> + <numerusform>Applicare la regola "%1" a %n messaggio?</numerusform> + <numerusform>Applicare la regola "%1" a %n messaggi?</numerusform> + </translation> + </message> + <message> + <source>You can undo this until the next search.</source> + <translation>Puoi annullare fino alla prossima ricerca.</translation> + </message> + <message> + <source>Apply rule "%1"</source> + <translation>Applica regola "%1"</translation> + </message> + <message> + <source>%1 failed: %2</source> + <translation>%1 non riuscito: %2</translation> + </message> + <message> + <source>%1: nothing to change.</source> + <translation>%1: nulla da cambiare.</translation> + </message> + <message> <source>About qtmaildir</source> <translation>Informazioni su qtmaildir</translation> </message> @@ -2733,6 +2780,14 @@ protocollo di rete: il recupero e l'invio sono comandi esterni. <translation>Rimuovi etichette</translation> </message> <message> + <source>Archive matching mail on arrival (removes inbox)</source> + <translation>Archivia all'arrivo la posta corrispondente (rimuove inbox)</translation> + </message> + <message> + <source>Without this, the sync hook skips a rule that removes inbox, since it runs unattended.</source> + <translation>Senza questa opzione, l'hook di sincronizzazione salta una regola che rimuove inbox, perché agisce senza supervisione.</translation> + </message> + <message> <source>Match &all</source> <translation>Corrispondono &tutte</translation> </message> @@ -2797,6 +2852,14 @@ protocollo di rete: il recupero e l'invio sono comandi esterni. <translation>Esegue la ricerca di questa regola nella finestra principale, per vedere quale posta raccoglie. Non etichetta nulla.</translation> </message> <message> + <source>&Apply to existing mail</source> + <translation>&Applica alla posta esistente</translation> + </message> + <message> + <source>Save, then apply this rule to mail already in the index. Asks first, naming how many messages would change. Undoable from the main window.</source> + <translation>Salva, poi applica questa regola alla posta già indicizzata. Chiede prima, indicando quanti messaggi cambierebbero. Annullabile dalla finestra principale.</translation> + </message> + <message> <source>Dismiss</source> <translation>Ignora</translation> </message> @@ -2807,6 +2870,13 @@ protocollo di rete: il recupero e l'invio sono comandi esterni. <numerusform>%n regole nel file richiedono attenzione: %1. Salva per riscriverle.</numerusform> </translation> </message> + <message numerus="yes"> + <source>The sync hook skips %n rule(s), which remove inbox or unread on mail nobody has looked at: %1. Tick "Archive matching mail on arrival" to let a rule remove inbox; unread is never removed on arrival.</source> + <translation> + <numerusform>L'hook di sincronizzazione salta %n regola, che rimuove inbox o unread su posta che nessuno ha visto: %1. Spunta "Archivia all'arrivo la posta corrispondente" per permettere a una regola di rimuovere inbox; unread non viene mai rimosso all'arrivo.</numerusform> + <numerusform>L'hook di sincronizzazione salta %n regole, che rimuovono inbox o unread su posta che nessuno ha visto: %1. Spunta "Archivia all'arrivo la posta corrispondente" per permettere a una regola di rimuovere inbox; unread non viene mai rimosso all'arrivo.</numerusform> + </translation> + </message> <message> <source>invalid</source> <translation>non valida</translation> |
