Skip to content

Add post: Architecting Runtime Multilingual Agents in Copilot Studio#290

Open
OwnOptic wants to merge 3 commits into
microsoft:mainfrom
OwnOptic:multilingual-architecture-post
Open

Add post: Architecting Runtime Multilingual Agents in Copilot Studio#290
OwnOptic wants to merge 3 commits into
microsoft:mainfrom
OwnOptic:multilingual-architecture-post

Conversation

@OwnOptic

@OwnOptic OwnOptic commented May 6, 2026

Copy link
Copy Markdown

Summary

Adds a new technical post on building runtime multilingual Copilot Studio agents using a single topic per intent, switched by Global.UserLanguage. The architecture was hardened on a 4-language production deployment (EN/FR/PT-BR/CS) for a global industrial client and extended to 8 languages (+ ES/NL/DE/IT) in a public demo to verify it scales without duplication.

The post focuses on patterns that are not in formal docs and that are easy to get wrong:

  • Global.UserLanguage as single source of truth (vs. relying on System.User.Language, which gets overridden by tenant locale in Teams)
  • Locale code normalization across Teams / WebChat / legacy connectors
  • JSON payload switching for per-language Adaptive Cards (with constraint notes on string length caps and Global vs Topic scope)
  • The SendActivity.activity: Power Fx evaluation gotcha - =Switch(...) does not evaluate when placed directly in activity:; the fix is a two-step SetVariable + SendActivity pattern. This caught me twice in production and is the single most useful thing in the post.
  • Generative orchestration for cross-lingual intent
  • Session reset that preserves language preference selectively

Also adds an emargot entry to _data/authors.yml. I am an external contributor (Microsoft Partner at Witivio, Team Lead Jumpstart for Copilot & Agents). Happy to adjust the author entry or any conventions if external authors are routed differently.

What's included

  • _posts/2026-05-06-multilingual-copilot-studio-architecture.md - the post
  • _data/authors.yml - author entry

No assets/images in this version - the post is self-contained text + code blocks. Happy to add diagrams (the demo agent screenshots) under assets/posts/multilingual-copilot-studio-architecture/ in a follow-up commit if useful.

Test plan

  • Front matter validates against existing post conventions (layout, title, date, categories, tags, description, author)
  • Author key emargot resolves to the new entry in _data/authors.yml
  • Markdown renders with Chirpy {: .prompt-info } and {: .prompt-warning } block styles
  • Code fences (powerfx, yaml) render with syntax highlighting
  • No broken Microsoft Learn links
  • Build passes locally (bundle exec jekyll build)

OwnOptic added 2 commits May 6, 2026 21:10
Adds a new post documenting a production-tested architecture for
runtime multilingual Copilot Studio agents. Covers:

- Global.UserLanguage as single source of truth
- Adaptive Card language picker pattern
- Locale code normalization across channels
- JSON payload switching for per-language Adaptive Cards
- The SendActivity activity: Power Fx evaluation gotcha
- Generative orchestration for cross-lingual intent
- Session reset preserving language preference

Hardened on a 4-language production deployment (EN/FR/PT-BR/CS) and
extended to 8 languages (+ ES/NL/DE/IT) in a public demo agent.

Also adds emargot author entry to _data/authors.yml.
- Move 4 production screenshots (language picker, FR card, topic canvas,
  FR response) into assets/posts/multilingual-copilot-studio-architecture/
  to match MSCAT image-hosting convention. Rewrite all paths to local
  /assets/posts/... including the front-matter header image.
- Add cross-link from the URL parameters bullet to the Apr 28 WebChat
  context-variables post since that is the canonical way to seed
  Global.UserLanguage from a host page.
- Drop the implicit promise of a downloadable demo agent. Reframe as
  the screenshots coming from a single-agent build extended to eight
  languages (production-grade, not a sample to ship).

@KarimaKT KarimaKT left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Change the title to reflect more precisely what this is, EX:
  • Robust Multilingual Pattern in Copilot Studio: Preselected Supported Languages and Custom Regional Behaviors.
    This allows space for articles on production-grade unsupported languages (there's an upcoming feature that will facilitate this) and articles on auto selection (the pattern in the GitHub sample)
  1. Although writing entire topics per language is indeed an antipattern, there is no mention of the standard localization method for regular messages and questions within topics: downloading the localization file and translating externally. This is important because it fits into the regional customization topic, and managing your own messages per region in localization files is the OOB way to do it.

  2. Reference the other article on multilingual Adaptive cards. This is important because that method allows you to write your adaptive card only once and to fit it into the OOB regional customization pipeline from section 2.

- Retitle to 'Robust Multilingual Pattern in Copilot Studio: Preselected
  Supported Languages with Custom Regional Behaviors' to scope the post
  to one specific strategy and leave room for upcoming articles on
  auto-detection and production-grade unsupported languages.
- Add scope callout clarifying what this pattern covers and what it
  deliberately does not.
- Acknowledge the OOB localization file workflow (export/translate/
  re-import) as the standard path for static topic strings, with a
  'when to use which' framing so readers can pick the right tool.
- Cross-link 'The One Card: Build Once, Speak All Languages' as the
  canonical playbook for Adaptive Card localization.
- Add a decision-factor comparison table between inline Switch() and
  the OOB localization file workflow, showing how they compose.
@OwnOptic

OwnOptic commented May 14, 2026

Copy link
Copy Markdown
Author

Update - just pushed the revisions (commit 71c3df2):

  1. Retitled to "Robust Multilingual Pattern in Copilot Studio: Preselected Supported Languages with Custom Regional Behaviors" - scopes the post to one specific strategy and leaves room for the auto-detection and unsupported-language articles.

  2. OOB localization file workflow now acknowledged. Added a "when to use which" callout up top and a decision-factor comparison table at the end. I'm keeping inline Switch() as the primary recommendation of this post, but it's now explicitly positioned as one of two valid approaches that compose well, rather than the only one. The framing: OOB localization file for in-topic message strings owned by translators, Switch() for runtime branching, system topics, and payload selection. The comparison table makes the tradeoffs explicit (source of truth, runtime branching capability, support for languages outside the official set, etc.).

  3. Adaptive Cards cross-link added - referenced The One Card: Build Once, Speak All Languages in both the scope callout and in the JSON switch section as the canonical playbook for plugging cards into the OOB localization pipeline. Also added an inline note next to the payload-switching pattern explaining when single-card-plus-localization is the better choice (mostly static text with a few dynamic values) versus payload switching (structurally different cards per language).

Let me know if you want any of the tradeoff framing adjusted.
@KarimaKT

@adilei

adilei commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Hey @OwnOptic, thanks for the post and the revisions — the production experience behind this is clear, and the activity: evaluation gotcha is a genuinely useful callout.

After reviewing, we're struggling to identify what this pattern offers beyond what the platform provides out of the box. Here's where we landed:

  • String translation (greetings, button labels, message text): The OOB localization file workflow handles this, including in system topics. SetTextVariable extends it to mixed content with variable placeholders — see The One Card for the full walkthrough.
  • Adaptive Card text: Static card text appears in the localization export automatically. Cards that only differ in wording (not structure) don't need per-language payloads.
  • GPT response language: The platform's generative orchestration already responds in the user's language — no prompt instructions needed.

The inline Switch(Global.UserLanguage, ...) pattern in the post is essentially reimplementing the localization file manually in Power Fx, which is more work to maintain and puts string ownership on the builder rather than a translation team.

The one area where Switch()/Condition nodes genuinely add value is regional logic branching — different business rules, different API endpoints, structurally different cards per locale. The post mentions this ("EU regulations, FR API endpoint") but only briefly. If that's the core problem you're solving, we'd love to see the post reframed around that, with concrete examples from your production deployment.

Is there a gap we're missing? Happy to discuss — it's possible there's a platform limitation in your scenario that isn't coming through in the current draft.

@adilei adilei self-requested a review May 18, 2026 09:04
@OwnOptic

Copy link
Copy Markdown
Author

Thanks @adilei, this helped me see where the intro is muddying the value prop.

The post is currently framed as "OOB is insufficient, here's an alternative," which isn't quite right. There are two specific gaps in the OOB pipeline that this pattern addresses, and the post should name them clearly instead of broadly positioning Switch() against the localization file.

Gap 1: Runtime user-driven language switching.

The localization file is resolved at build/channel time. It works well when the channel reliably reports the user's locale and that locale is stable for the session. It doesn't have a hook for "the user just picked French from an Adaptive Card, switch the rest of the session." That's where Global.UserLanguage + Switch() does work the localization file can't do. Two channels make this come up often in practice: Teams (tenant locale frequently overrides user preference) and embedded WebChat (host page may or may not pass a locale). Both push toward explicit user selection, which pushes toward a runtime variable.

Gap 2: Regional logic branching, not string substitution.

This is the one you flagged. In the production deployment, French users called a different Power Automate flow than English users (different backend, regional data residency), and Czech had a regulatory disclaimer that was a structurally different content block, not a translation of the EN one. The localization file substitutes strings inside a fixed execution path; it doesn't fork the path. Condition nodes + Switch() do.

Cards specifically: I agree the single-card + SetTextVariable pattern from The One Card is the better default when only the wording changes. Payload switching is for cards that are structurally different per locale (different fields, different actions, layout differences for languages that expand or contract significantly). I'll make that distinction sharper in the post rather than letting the two read as interchangeable.

Planned revision:

  • Rewrite the intro around the two named gaps above
  • Move the comparison table to right after the intro so readers can self-select in 30 seconds
  • Add a "responds to runtime user language choice" row to the table
  • Expand Section 3 with the FR-flow / CZ-disclaimer examples instead of the current parenthetical
  • Keep the activity: Power Fx gotcha and the selective session reset as-is

If you'd prefer this as two posts (one on runtime switching, one on regional branching), I'm open to that too. Let me know if the framing above works before I push the revision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants