You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(calendar): translate task title and description server-side (#1384) - #1405
v2 of #1324. Task titles and descriptions are now translated server-side, so every entry point fills a recipient's missing language, not only the calendar dialog.
Where it runs
Task wizard CreateTask/UpdateTask: the translation is filled into the request before anything is saved. This covers the calendar, the task-list batch actions and the Task Wizard page.
CalendarAssignmentReconciliationService.ReconcileEventAsync: fills the database translations once the effective recipients are known. This covers team-only tasks, whose team links are written after the wizard saves, and members who join a team later.
UpdateDeviceUser language change: fills only that worker's events, both explicit ones and team events on a property the worker is linked to. It deploys nothing. This step and the tag-delta reconcile now run as the last step of a successful save, after SiteUpdate and the login writes. A failure is logged and never fails the save. It is not retried by that worker's next save; the affected events catch up on their next reconcile (a calendar save of the event or a later tag change), the same gap as before, when a throwing reconcile failed the save after the tags were written.
Rules (same as v1)
Only an empty title or description is filled, from the Danish text. Nothing typed by hand or translated earlier is overwritten, and a changed Danish text is not re-translated.
Targets are the active non-Danish languages of the recipients. Danish is found by its language code, never by id, and a da language is never a target.
Translator
It is the host's eFormApi.BasePn ITranslationService, resolved optionally. With no service, no API key, or a host BasePn without the type, the task is saved in Danish.
In that case the save reports TaskSavedWithoutTranslation in the wizard and calendar success messages and in the task-list batch summary.
Per filler instance, each (text, target) is translated once. After the translator throws, no further calls are made.
No gRPC path reaches the wizard.adhoc.proto creates single-title ad-hoc tasks, and events.proto/calendar.proto only read. So there is no gRPC-created task to translate.
Tests
New TaskServerSideTranslationTests run the real wizard, calendar, task list, reconcile and assignment-worker helper with a fake BasePn ITranslationService that returns [en-US] <text>:
Calendar create for an English worker stores English in both AreaRuleTranslation and PlanningNameTranslation, and never asks for target da.
A team-only task with an English member gets English.
A task-list batch AddWorker that adds an English worker fills English.
A manual English title is kept; only the empty description is translated.
An update that leaves out a stored English text keeps it.
With no translator, with an unconfigured one, with one that answers unsuccessfully, and with one that throws: the task saves in Danish only and the notice is present, including in the batch summary.
An English worker joining the team: ReconcileEventsForWorkerTagsAsync fills English.
A language change in UpdateDeviceUser fills only that worker's events, deploys nothing, and calls no reconcile.
A reconcile that throws during UpdateDeviceUser does not fail the save.
Not verified
Built locally against a scratch-packed BasePn 10.0.37; tests run in CI only.
The guard for an older host without the BasePn type was reasoned through, not exercised.
v2 of #1324. Every entry point now fills an assignee's missing language,
not only the calendar dialog: the task wizard's CreateTask/UpdateTask (the
calendar, the task-list batch actions, the Task Wizard page) and the
reconcile that expands teams (team links written after the wizard, members
joining later).
Rules as in v1: only an empty title or description is filled, from the
Danish text; nothing typed or translated earlier is overwritten. Targets
are the active non-Danish languages of the recipients; Danish is found by
its code, and a 'da' language is never a target.
The translator is the host's eFormApi.BasePn ITranslationService, resolved
optionally: no service, no API key, or a BasePn without the type leaves the
task in Danish, and the save reports TaskSavedWithoutTranslation (wizard,
calendar and task-list batch summary). Per filler instance a translation is
asked for once per (text, target), and a throwing translator is not called
again.
A device user's language change fills that worker's events (explicit, or
via a team on a linked property) without deploying anything; the tag
reconcile and the translation run as the last step of a successful save,
after SiteUpdate and the login writes, and never fail it.
No gRPC path reaches the wizard: adhoc.proto creates single-title ad-hoc
tasks, events.proto and calendar.proto only read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow (#1384)
The host turns a timeout into an unsuccessful answer, so tripping only on
an exception let a down translator cost one timeout per (text, target).
The filler now also stops after two unsuccessful answers in a row; a
success resets the count, so one unsupported language does not stop the
others. Tests pin both: two languages with a failing translator make two
calls, and a failing title between successful descriptions does not stop
the filler.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds server-side task title/description translation so all task creation/update entry points populate recipients’ missing languages, and surfaces a consistent “saved without translation” notice when translation is unavailable.
Changes:
Introduces ITaskTranslationFiller / TaskTranslationFiller plus TaskTranslationNotice to fill missing translations and append/recognize the “saved without translation” notice.
Integrates translation filling into task wizard save paths, reconciliation, calendar create/update, and device-user language change follow-up.
Adds integration tests covering wizard/calendar/task-list/reconcile/device-user scenarios and bumps BasePn dependency for host translation service.
Both device-user tests created "Jane Doe" in the same fixture database, so
the second CreateDeviceUser was refused with TheEmployeeAlreadyExist.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The claimed retry cannot happen after this call fails. The site language and tag rows have already been persisted, so the next identical save computes languageChanged == false and an empty changedTagIds; SyncCalendarAfterDeviceUserUpdateAsync then performs neither repair. A transient failure can therefore leave missing translations or stale team deployments indefinitely. Persist pending reconciliation work (including removed tag IDs) before swallowing the exception, or otherwise make subsequent saves reconcile recoverable current state.
Make partial translation failure notice field-neutral
This notice is also emitted for partial failures: one field may already have a manual translation, or one translator call may succeed while the other fails. In those cases it is incorrect to tell the user that both title and description were saved only in Danish. Please make every localized value field-neutral, e.g. state that some requested translations could not be saved.
…d by the next save (#1384)
The comment and log line claimed the next save retries it; it cannot, as
the tags and language are already stored and that save sees no change.
The events catch up on their next reconcile. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This loop performs up to two sequential remote translation calls for every active event during UpdateDeviceUser. The event set is unbounded, and successful calls never trip the failure cap, so a worker with many uniquely titled events can keep the save request open for hundreds of network round trips. Queue this non-critical post-save work or process it through a bounded/batched translation job instead of awaiting the entire history in the HTTP request.
Reactivation omits newly added planning name translations
When this adds a new target-language entry while an inactive task is reactivated, the later false -> true status branch only loops over existing planning.NameTranslations (around line 1136); it never inserts the newly added language. The AreaRuleTranslation is therefore saved, but PlanningNameTranslation—which supplies the device label—remains missing on the direct Task Wizard update path. Upsert names from updateModel.Translates in the reactivation branch as the active-to-active branch does.
Partial translation failures produce an inaccurate notice
This notice is also emitted for partial failures—for example, when a manual title is retained and only description translation fails, or one language fails while another succeeds. Saying both fields were saved only in Danish is then incorrect. Use wording such as “Some missing translations could not be generated; untranslated fields remain in Danish” consistently across the localized variants.
…ded language (#1384)
The wizard's inactive -> active branch only updated existing
PlanningNameTranslation rows from the area rule, so a language added in
the same save (typed, or filled by the server-side translation) got its
AreaRuleTranslation but no planning name - the one the device label reads -
before Pair deployed. Create the missing rows there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For an active task, |= keeps the wizard's failure flag even when this final reconcile successfully fills every missing explicit/team translation. Production gives the wizard and reconciler different transient filler instances, so a transient first failure can recover here while the response still falsely says the task was saved in Danish only. Recompute the final completeness after reconciliation (while retaining the wizard result for inactive tasks, which reconciliation skips).
This issue also appears on line 2425 of the same file.
Team-only recipients skipped in thisAndFollowing path
The thisAndFollowing path returns after running only the wizard filler, which sees explicit Sites but not team members. Because this branch intentionally never calls the reconciler, a team-only task whose translation previously failed still remains untranslated (and reports no notice) after this calendar save, despite the stated catch-up-on-next-calendar-save behavior. Resolve the effective team recipients and fill them before returning, without triggering deployment if that remains out of scope for this edit mode.
Fallback language ID skips source translation on shifted SDK IDs
When the frontend falls back to language id 1, a tenant whose SDK id 1 belongs to another language never reaches the source translation: RemapCommonTranslationLanguageIdsAsync deliberately leaves every existing id unchanged, so this lookup finds no Danish row and returns true. The task is then saved without recipient translations and without the promised notice. This is especially relevant on the shifted-id installations documented in AreaRuleLanguageHelper.cs:104-115; normalize the fallback source by the SDK language code before treating the fill as complete.
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
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
v2 of #1324. Task titles and descriptions are now translated server-side, so every entry point fills a recipient's missing language, not only the calendar dialog.
Where it runs
CreateTask/UpdateTask: the translation is filled into the request before anything is saved. This covers the calendar, the task-list batch actions and the Task Wizard page.CalendarAssignmentReconciliationService.ReconcileEventAsync: fills the database translations once the effective recipients are known. This covers team-only tasks, whose team links are written after the wizard saves, and members who join a team later.UpdateDeviceUserlanguage change: fills only that worker's events, both explicit ones and team events on a property the worker is linked to. It deploys nothing. This step and the tag-delta reconcile now run as the last step of a successful save, afterSiteUpdateand the login writes. A failure is logged and never fails the save. It is not retried by that worker's next save; the affected events catch up on their next reconcile (a calendar save of the event or a later tag change), the same gap as before, when a throwing reconcile failed the save after the tags were written.Rules (same as v1)
dalanguage is never a target.Translator
ITranslationService, resolved optionally. With no service, no API key, or a host BasePn without the type, the task is saved in Danish.TaskSavedWithoutTranslationin the wizard and calendar success messages and in the task-list batch summary.No gRPC path reaches the wizard.
adhoc.protocreates single-title ad-hoc tasks, andevents.proto/calendar.protoonly read. So there is no gRPC-created task to translate.Tests
New
TaskServerSideTranslationTestsrun the real wizard, calendar, task list, reconcile and assignment-worker helper with a fake BasePnITranslationServicethat returns[en-US] <text>:AreaRuleTranslationandPlanningNameTranslation, and never asks for targetda.AddWorkerthat adds an English worker fills English.ReconcileEventsForWorkerTagsAsyncfills English.UpdateDeviceUserfills only that worker's events, deploys nothing, and calls no reconcile.UpdateDeviceUserdoes not fail the save.Not verified
Refs #1384
🤖 Generated with Claude Code