Localising PCF Controls and Dynamics 365 for a Multi-Country Rollout

A technical guide for the developer shipping a PCF control in many locales: resx files in the manifest, user language from context, plurals and placeholders, locale formatting, right to left layout and a translation workflow that stays in sync.

To localise a PCF control for a multi-country Dynamics 365 rollout, move every visible string into resx files declared in the manifest, one per language such as strings/Control.1033.resx, and read them with context.resources.getString. Take the language from context.userSettings.languageId, never from the browser. Write strings with named placeholders and plural variants so translators can reorder them. Format dates, numbers and currency from context.userSettings and context.formatting instead of hard-coded patterns, and flip layout when context.userSettings.isRTL is true. Leave room for longer translations. Then run it as a process: extract strings from the hard-coded control, keep every language file in sync on each release, and wrap lookups in a fallback chain so a missing translation shows English rather than a blank.

Why do PCF controls break when Dynamics 365 rolls out to more countries?

Because most controls are written for the first country and nobody revisits them. The forms, views and option set labels around a control can be translated through the platform, but a code component renders its own text and formatting, so it stays in the author's language and conventions while the rest of the form changes around it. The typical findings are English labels on an otherwise German form, dates shown month first to users who read day first, amounts with the wrong decimal separator or currency symbol, text cut off because a translation is longer, and a layout that does not mirror for Arabic or Hebrew users.

The harder part is organisational. The original author has often moved on, strings are scattered through React components, and each new country arrives as a separate request. This page covers the control itself. Which parts of the system should be identical across countries and which stay local, and how a core solution with regional extensions is structured, is set out in our guide to standardising Dynamics 365 across regions.

How do resx files work in a PCF control manifest?

Each language gets its own resx file, named with the Windows language code identifier (LCID) of that language, and every file is declared in the resources element of ControlManifest.Input.xml, for example <resx path="strings/Control.1033.resx" version="1.0.0" /> for English and <resx path="strings/Control.1031.resx" version="1.0.0" /> for German. The platform loads the file that matches the user's language, and the control reads values by key with context.resources.getString("Key").

The same keys serve the manifest itself: display-name-key and description-key on the control and on each property point to resx entries, so the names makers see when configuring the control are translated too. Declaring files, bumping the manifest version and the rest of the manifest contract are covered in our PCF controls development guide.

  • Keep one source language file, usually 1033 English, as the master list of keys, and treat every other file as a translation of it.
  • Use stable, meaningful keys such as Grid_EmptyState_Message, never the English text as the key, so changing the English wording does not break every translation.
  • Add a comment to each resx entry describing where the text appears and what any placeholder means; translators work without seeing the control.
  • Declare every language file in the manifest. A file present in the folder but missing from resources is not deployed.
  • Do not assume what getString returns for a missing key in every host and release; wrap it, as described in the fallback section below, and check current Microsoft documentation.

Why must the control use the user language from context and not the browser?

Because Dynamics 365 has its own language setting per user, and it is often different from the browser. A user in a shared service centre can run an English browser with Dynamics 365 set to Polish, and a user travelling can open the app on a borrowed device. context.userSettings.languageId gives the LCID of the language the user has chosen in Dynamics 365, which is the same language the platform uses to pick the resx file and to render the rest of the form. navigator.language and the Accept-Language header describe the browser, so a control that uses them can disagree with the form it sits in.

Formatting is a separate choice from language. A user can work in English while using German date and number formats, and context.userSettings.dateFormattingInfo and numberFormattingInfo reflect those format settings. Read both, and do not derive one from the other.

Where you need the JavaScript Intl APIs, for example Intl.PluralRules or Intl.ListFormat, they expect a BCP 47 tag such as de-DE rather than an LCID. Keep a small mapping from the LCIDs you support to BCP 47 tags inside the control, derived from the user settings, and never fall back to navigator.language to fill a gap.

How do you write strings that survive pluralisation and translation?

Write whole sentences with named placeholders, and give translators every plural form their language needs. String concatenation such as "Showing " + count + " of " + total + " records" fixes English word order in code, and many languages need the number in a different position or a different noun form for different counts.

ProblemFragile approachTranslation-safe approach
Word orderConcatenating fragments around valuesOne string with named placeholders, such as "Showing {shown} of {total} records"
PluralsA singular and a plural string chosen with count === 1Keys per plural category, such as Records_one and Records_other, chosen with Intl.PluralRules for the user language; some languages need few and many forms
Reused wordsOne "Open" string used for a button and a statusSeparate keys per meaning, because other languages may use different words
PlaceholdersPositional %s or {0} tokensNamed tokens that translators can move and that a check can verify in every language file
Text in images or CSSLabels drawn into icons or added with CSS contentText in the markup from resx, with icons as decoration only
SortingDefault string comparisonIntl.Collator with the user language, so accented and non Latin characters sort as users expect

How should dates, numbers and currency be formatted per locale?

From the user's settings, never from a hard-coded pattern. context.formatting provides helpers that apply the Dynamics 365 user settings, such as formatDateShort, formatDateLong, formatTime, formatDecimal, formatInteger and formatCurrency, and context.userSettings.dateFormattingInfo and numberFormattingInfo expose the separators, patterns and symbols behind them for anything the helpers do not cover. For a bound column, the property's formatted value is already rendered by the platform, which is often the simplest correct choice for display.

Time zones need the same discipline. Dataverse stores most date and time columns in UTC, and the user's time zone is a Dynamics 365 setting, so use the context.formatting conversion helpers rather than the browser's local time. Date only columns have their own behaviour and should not be shifted by a time zone conversion at all.

Currency is where multi-country data goes wrong most visibly. A record in Dataverse carries its own transaction currency, so a control showing an amount should use that record's currency, not the user's default currency symbol. Where a group currency view is also needed, show the base currency value explicitly and label it, rather than converting in the control with a rate it does not own.

How do you support right to left languages and longer translations?

Read context.userSettings.isRTL and set dir="rtl" on the control's root element when it is true, then build the styles so the direction change flips the layout rather than fighting it. Text expansion needs the same flexibility: many translations are noticeably longer than English, and a fixed-width button or column that fits "Save" will not fit its equivalent in every language.

  • Use CSS logical properties such as margin-inline-start, padding-inline-end and inset-inline-start instead of left and right, so spacing mirrors automatically.
  • Mirror directional icons such as arrows and chevrons in RTL, but not icons that are not directional, such as a clock or a checkmark.
  • Fluent UI supports RTL; make sure the direction reaches its provider or root rather than only your own markup.
  • Keep numbers, codes and email addresses readable inside RTL text by isolating them, for example with the bdi element.
  • Let buttons, labels and column headers grow and wrap; never fix their width to the English text.
  • Truncate only where the full text is available on hover, focus or tap, and test the longest language you support, not the shortest.
  • Narrow layouts and fixed widths interact with expansion, so apply the sizing rules in making PCF controls work on phones and small screens at the same time.

How do you extract strings from a control that hard-coded them?

Systematically, in one pass, before any translation is ordered. Translating a control that still has hard-coded strings pays twice, because the missed strings surface one country at a time.

  • Find every visible literal: JSX text, aria-label and title attributes, placeholder text, validation and error messages, empty states, confirmation dialogs and strings built in helper functions. A lint rule that flags string literals in JSX, such as no-literal-string from eslint-plugin-i18next, speeds this up.
  • Move each one into the source resx file with a stable key and a translator comment, replacing concatenation with named placeholders as you go.
  • Add a thin localisation helper that the whole control uses for lookups, placeholders and plurals, so there is one place to change behaviour later.
  • Replace hard-coded date, number and currency formatting with context.formatting or the user settings at the same time.
  • Build a pseudo-locale file that wraps and lengthens every string, for example with accented characters and extra padding. Any English text still visible with it active is a missed string, and any clipped text is an expansion problem.
  • Keep accessibility text in scope; screen reader labels in the wrong language are a defect, as covered in retrofitting accessibility into PCF controls.

How do you keep translations in sync across releases and avoid blank labels?

Make the source language file the contract and check every other file against it in the build. A developer who adds a key to English and forgets the others should fail the pipeline, not ship a control with blanks in Portuguese.

The fallback chain is the safety net for anything that still slips through. Wrap context.resources.getString in the control's helper so that an empty result, or a result equal to the key, falls back to the source language string compiled into the bundle, and only then to the key itself, which should also be logged so it can be fixed. Users then see English text in the worst case, never an empty button.

  • A build script compares keys across all resx files and fails on missing keys, extra keys and placeholder names that differ between languages.
  • New and changed source strings are exported for translation as a batch at a known point in each release, not one string at a time.
  • Changed English wording gets a new key or is flagged for re-translation, so an outdated translation does not silently remain.
  • The manifest version and solution version are bumped with any resx change, so environments load the new strings.
  • One owner decides terminology per language, ideally the same person or team who owns the Dynamics 365 label translations, so the control and the form use the same words.

What has to be translated on the Dynamics 365 side as well?

Everything the platform renders around the control, which is handled through the platform's own translation tools rather than resx files. An administrator first enables the languages needed in the environment; the base language is fixed when the environment is created, so that decision belongs early in the rollout. Users then choose their language in their personal settings.

Labels for tables, columns, choices, forms, views and other solution components are translated by exporting translations from the solution, filling in the exported file for each enabled language, and importing it back. Keep that export and import inside the same release process as the control, in the development environment that owns the solution, so translated labels travel with managed solutions rather than being typed into production. Some components, such as email templates, knowledge articles and data in your own tables, have separate localisation approaches. Check current Microsoft documentation for the exact steps and which components each covers, and see our guide to Power Platform ALM with PAC CLI for the release path itself.

Should a multi-country rollout run on Dynamics 365 or a custom-built CRM?

For groups that already run Dynamics 365 Customer Engagement or Power Platform, localising the platform and its code components is usually far cheaper than changing platform: languages, formatting settings, multiple currencies and label translation are built in, and the work is making custom components behave as well as the standard ones. The regional architecture decisions are covered in the multi-region guide linked above.

We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both. Where some countries cannot justify per-user Microsoft licensing, or need a system outside the Microsoft ecosystem, a custom-built CRM on React, Node.js, PostgreSQL or .NET can be designed for multiple languages, locales and currencies from the first release, using the same principles described on this page.

How does Solzet help localise PCF controls for a multi-country rollout?

We start with an inventory: every custom control in the solution, which forms and countries use it, and a pseudo-locale run that shows every hard-coded string, format and clipped label. Then we extract strings into resx files, add the localisation helper and fallback chain, replace hard-coded formatting with the user settings, fix RTL and text expansion, and add the build check that keeps language files in sync. Translations from your vendor or regional teams are loaded and verified on real forms in each language before release, alongside the Dynamics 365 label translations.

The work is done by senior consultants and full-stack developers delivering remotely from Yerevan, Armenia, with 8+ years of Dynamics 365 Customer Engagement and Power Platform work, directly for your team or white-label for Microsoft partners. New controls and wider rebuilds sit with our PCF controls development service.

What do people ask us?

How do I localise a PCF control into multiple languages with resx files?

Create one resx file per language named with its LCID, such as strings/Control.1033.resx and strings/Control.1036.resx, declare each in the manifest resources element, and read strings with context.resources.getString using stable keys. Use the same keys for display-name-key and description-key in the manifest, bump the manifest and solution versions, and wrap lookups in a helper that falls back to the source language.

Should a PCF control use navigator.language to detect the user language?

No. Dynamics 365 has its own per-user language setting, which can differ from the browser. Use context.userSettings.languageId, which is the language the platform uses for the form and for choosing the resx file. For Intl APIs, map the supported LCIDs to BCP 47 tags inside the control rather than falling back to the browser language.

How do I format dates and currency correctly in a PCF control?

Use context.formatting helpers such as formatDateShort and formatCurrency, or the formatted value of a bound property, which apply the user's Dynamics 365 settings. For custom needs, read context.userSettings.dateFormattingInfo and numberFormattingInfo. Show amounts in the record's transaction currency, and use the platform time zone conversion helpers rather than the browser's local time.

How do I handle plurals in a translated PCF control?

Store a key per plural category, for example Items_one and Items_other, plus the extra categories languages such as Polish or Arabic need, and choose between them with Intl.PluralRules for the user language. Put the count in a named placeholder inside the full sentence so translators can move it, and check in the build that every language file has matching placeholders.

How do I support right to left languages in a PCF control?

Read context.userSettings.isRTL and set dir="rtl" on the control root when it is true. Use CSS logical properties instead of left and right, pass the direction to Fluent UI, mirror directional icons only, and isolate numbers and codes inside RTL text. Test with a real RTL language on a real form, not only with the direction attribute toggled.

What happens if a translation is missing from a resx file?

Do not rely on unspecified behaviour. Wrap context.resources.getString in a helper that treats an empty result, or the key returned as text, as missing, falls back to the source language string, and logs the key. Add a build check that fails when any language file lacks a key from the source file, so gaps are caught before release.

Do resx files translate the Dynamics 365 form labels around the control?

No. Resx files cover the control's own text and its manifest names. Table, column, choice, form and view labels are translated through the platform: enable the languages in the environment, export translations from the solution, translate the file and import it back. Keep both in the same release process so the control and the form use the same terminology.

Which solution is right for your business?

Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.