Files
fa5d470ae4 MUL-7706 feat(wecom): every notice reads the language of whoever will read it (#8831)
* feat(wecom): every notice reads the language of whoever will read it

The adapter's user-visible strings were Chinese literals scattered across
replier.go, inbox_message.go and outbound_media.go, so an English-reading member
got Chinese notices whatever their profile said. copyPack already existed for
the bubble's five stream strings; this moves the rest into it and resolves the
locale from the destination rather than the deployment.

Who decides, per surface:
  - a 1:1 reply, the binding prompt, the inbox card: the reader's own profile
  - a group notice: the room, which reads the deployment's language
  - an attachment-failure notice: resolved on the request path and carried on
    attachmentTarget, because the failure happens on a detached goroutine with
    no context left to read a profile with

TestOnlyStringsGoHoldsUserVisibleCopy walks the package's AST and fails on any
Han literal outside strings.go. Two files are listed as pending with an exact
count: wecom_channel.go's unsupported-kind receipt and media_ingest.go's two
media notices, each moved by its own follow-up. The count is asserted both ways,
so the allowance cannot outlive the follow-up that consumes it.

* feat(wecom): every notice reads the language of whoever will read it

The adapter's user-visible strings were Chinese literals scattered across
replier.go, inbox_message.go and outbound_media.go, so an English-reading member
got Chinese notices whatever their profile said. copyPack already existed for
the bubble's stream strings; this moves the rest into it and resolves the locale
from the destination.

Who decides, per surface:
  - a 1:1 reply, the binding prompt, the inbox card: the reader's own profile
  - a group notice: the room, which reads the deployment's language
  - an attachment-failure notice: resolved on the request path and carried on
    attachmentTarget, because the failure runs on a detached goroutine with no
    context left to read a profile with

The wiring is the part that has to hold: router.go passes Languages, and
NewOutboundReplier warns when it is nil, because a missing lookup has no other
symptom — nothing errors, nothing is empty, every notice simply comes out in one
language. TestWecomReplierGetsItsLanguageLookupOnTheRealBootPath asserts it off
NewRouter itself, with the anti-vacuity check read as a delta between two
routers rather than a count, since chat:done has listeners of its own.

TestOnlyStringsGoHoldsUserVisibleCopy walks the package AST and fails on any Han
literal outside strings.go, naming the two files whose copy has not moved yet
with an exact count, asserted both ways.

* MUL-7706 docs(wecom): drop the duplicated stream-field docs, refresh the lint header

The restored StreamNoReply block carried its opening paragraph twice, and the
lint file's header still described two rules and a greeting after the second
rule was dropped.

Co-authored-by: multica-agent <github@multica.ai>

* MUL-7706 test(wecom): compare two routers in the replier wiring guard

A bare chat:done count is non-zero whenever Slack or DingTalk is configured, so
the guard passed with the WeCom block never entered. Build one router without
the WeCom key and one with it, as wecom_bubble_wiring_test.go does, and require
the difference.

Co-authored-by: multica-agent <github@multica.ai>

* fix(wecom): a relayed reply's attachment notice reads the reader's language too

deliverRelayed builds its own attachmentTarget and never set Locale, so
copyFor("") fell back to the deployment pack. On a multi-replica deployment
that is the common path, not the exception: chat:done lands wherever the run
finished, and only the lease holder can write to the socket — so an English
reader whose file fails to upload got the Chinese notice, while the same
failure on the direct path was already answered in English.

Nothing else differs when this is wrong. The file still fails, the notice still
goes out, and no existing test covered the relayed case.

Also two wording fixes against the product glossary: /issue creates an issue,
not a task, so IssueUsage now reads like Slack's; and the task_failed inbox
label is 'Run failed', matching what the web inbox shows for the same
notification.

* MUL-7706 test(wecom): keep each attachment-notice test under its own doc comment

The relayed test was inserted between the direct test's doc comment and its
function, so the direct test lost its comment to the relayed one. Put the
direct test back under its comment, follow it with the relayed test, and move
the util import out of the standard-library group.

Co-authored-by: multica-agent <github@multica.ai>

* MUL-7706 test(wecom): the invoke-denied notice reads its one reader's language

A 1:1 refusal joins the per-outcome locale table; a group trigger's refusal,
sent to the sender's own 1:1, must read the sender's profile rather than the
room's deployment default.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Bohan-J <bhjiang@outlook.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-09-27 23:00:52 +08:00
..