Localized Resource
A Localized Resource defines a multi-language string that can be dynamically retrieved based on the user's language preference. Each resource holds one translation per language, and is resolved to the right language at runtime. This enables internationalization (i18n) of your business logic without hardcoding text in a single language.
The Challenge of Multi-Language Applications
Organizations operating globally or in multilingual regions face a common challenge: how to present information to users in their preferred language. This goes beyond translating the user interface - business logic itself often produces text that users see:
- Validation error messages ("This field is required")
- Email notification content ("Your order has been approved")
- Generated document text ("Invoice", "Total", "Due Date")
- Status descriptions ("Pending Approval", "Completed")
- Business rule explanations ("Amount exceeds credit limit")
Hardcoding these strings in a single language creates a poor experience for users who speak other languages. But managing translations scattered throughout your logic is a maintenance nightmare.
The Solution: Centralized Localized Resources
Localized Resources provide a centralized, structured approach to managing multi-language text:
- Define strings once with a unique key/index
- Provide translations for each supported language
- Reference by key in your Logic Blocks and Flows
- Automatic resolution returns the correct language based on user context
When your logic needs to display text, it references the Localized Resource by key. Logic Composer automatically returns the appropriate translation based on the current user's language setting in Dataverse.
Structure
A Localized Resource consists of:
| Component | Description |
|---|---|
| Name | Unique identifier for the resource - and the key you use to look it up (e.g., "RequiredField", "OrderApproved") |
| Description | Documentation explaining what this string is for |
| Project | The Flowon Project this resource belongs to |
| Language Values | One translated string per language, each keyed by that language's LCID (locale ID, an integer - e.g., 1033 for English, 1036 for French) |
A Localized Resource represents a single string with its translations - not a container of many keyed entries. To manage a family of related messages, create one Localized Resource per message and use a consistent naming convention (for example, Validation_RequiredField, Validation_InvalidEmail). The examples further down group related resources by a shared name prefix purely for readability.
Language Values
Each Localized Resource stores its translations as a set of language / value pairs:
| Property | Description |
|---|---|
| Language (LCID) | The integer locale ID identifying the language (e.g., 1033 English, 3082 Spanish, 1036 French, 1031 German) |
| Value | The translated text for that language |
A resource does not declare a fixed list of "supported languages" - it simply contains a value for each language you provide.
Placeholders for Dynamic Content
Strings often need to include dynamic values - an amount, a name, a date. A common convention is to embed placeholder tokens in the translated text and substitute the actual values at runtime using an expression function such as FORMAT:
| Placeholder | Description |
|---|---|
{0} | First dynamic value |
{1} | Second dynamic value |
{2} | Third dynamic value |
| ... | Additional placeholders as needed |
The placeholder positions must remain consistent across all translations, though the surrounding text can differ based on language grammar.
Example with placeholders:
| Key | English | Spanish | French |
|---|---|---|---|
| AmountExceeded | "Amount 0 exceeds limit of 1" | "El monto 0 excede el límite de 1" | "Le montant 0 dépasse la limite de 1" |
| WelcomeUser | "Welcome, 0!" | "¡Bienvenido, 0!" | "Bienvenue, 0!" |
| OrderShipped | "Order 0 shipped on 1" | "Pedido 0 enviado el 1" | "Commande 0 expédiée le 1" |
At runtime you first retrieve the localized string for the resource, then apply the values with FORMAT. For example, resolving AmountExceeded and formatting it with "$15,000" and "$10,000" produces "Amount $15,000 exceeds limit of $10,000" (or the equivalent in the user's language). The retrieval step returns the raw translated string for the resolved language; the substitution is done by the expression, not by the resource itself.
Try It Live
Select a language to watch all strings resolve instantly. The amber {0} tokens show where placeholders appear - the resolver at the bottom shows how they get substituted with actual values at runtime.
Language Resolution
When a Localized Resource string is requested, the language is resolved as follows:
- Explicit language: If the request specifies a language (an LCID), that language is used.
- User's Language Setting: Otherwise, the current user's Dataverse UI language is used.
If no translation exists for the resolved language, an empty string is returned. For this reason, always provide values for the languages your users actually use so a meaningful string is available.
Using Localized Resources
Localized Resources can be used in:
- Validation error messages: Return localized messages when validation fails
- Flow steps: Use localized text in email bodies, notifications, and generated content
- Raise Error step: Provide localized error messages
- Any expression: Reference localized strings wherever text is needed
Example: Validation Messages Resource
Resource Group: ValidationMessages
(each row below is an individual Localized Resource, grouped by name prefix)
Purpose: "Error messages displayed when data validation fails"
Languages provided: English, Spanish, French, German, Portuguese
Resources:
┌────────────────────┬────────────────────────────────────────────────────────────────────┐
│ Key │ Translations │
├────────────────────┼────────────────────────────────────────────────────────────────────┤
│ RequiredField │ EN: "This field is required" │
│ │ ES: "Este campo es obligatorio" │
│ │ FR: "Ce champ est requis" │
│ │ DE: "Dieses Feld ist erforderlich" │
│ │ PT: "Este campo é obrigatório" │
├────────────────────┼────────────────────────────────────────────────────────────────────┤
│ InvalidEmail │ EN: "Please enter a valid email address" │
│ │ ES: "Por favor ingrese una dirección de correo válida" │
│ │ FR: "Veuillez entrer une adresse email valide" │
│ │ DE: "Bitte geben Sie eine gültige E-Mail-Adresse ein" │
│ │ PT: "Por favor, insira um endereço de email válido" │
├────────────────────┼────────────────────────────────────────────────────────────────────┤
│ AmountExceeded │ EN: "Amount exceeds the maximum limit of {0}" │
│ Placeholders: {0} │ ES: "El monto excede el límite máximo de {0}" │
│ = Maximum Amount │ FR: "Le montant dépasse la limite maximale de {0}" │
│ │ DE: "Der Betrag überschreitet das Maximum von {0}" │
│ │ PT: "O valor excede o limite máximo de {0}" │
├────────────────────┼────────────────────────────────────────────────────────────────────┤
│ DateInPast │ EN: "Date cannot be in the past" │
│ │ ES: "La fecha no puede ser en el pasado" │
│ │ FR: "La date ne peut pas être dans le passé" │
│ │ DE: "Das Datum darf nicht in der Vergangenheit liegen" │
│ │ PT: "A data não pode estar no passado" │
├────────────────────┼────────────────────────────────────────────────────────────────────┤
│ InvalidRange │ EN: "{0} must be between {1} and {2}" │
│ Placeholders: │ ES: "{0} debe estar entre {1} y {2}" │
│ {0} = Field Name │ FR: "{0} doit être compris entre {1} et {2}" │
│ {1} = Min Value │ DE: "{0} muss zwischen {1} und {2} liegen" │
│ {2} = Max Value │ PT: "{0} deve estar entre {1} e {2}" │
└────────────────────┴────────────────────────────────────────────────────────────────────┘
Example: Email Templates Resource
Resource Group: EmailTemplates
(each row below is an individual Localized Resource, grouped by name prefix)
Purpose: "Email subject lines and body content for automated notifications"
Languages provided: English, Spanish, French
Resources:
┌─────────────────────────┬────────────────────────────────────────────────────────────────┐
│ Key │ Translations │
├─────────────────────────┼────────────────────────────────────────────────────────────────┤
│ OrderConfirmSubject │ EN: "Order Confirmation - #{0}" │
│ Placeholders: {0} │ ES: "Confirmación de Pedido - #{0}" │
│ = Order Number │ FR: "Confirmation de Commande - #{0}" │
├─────────────────────────┼────────────────────────────────────────────────────────────────┤
│ OrderConfirmBody │ EN: "Dear {0},\n\nThank you for your order #{1}.\n │
│ Placeholders: │ Your order total is {2}.\n\nBest regards" │
│ {0} = Customer Name │ ES: "Estimado/a {0},\n\nGracias por su pedido #{1}.\n │
│ {1} = Order Number │ El total de su pedido es {2}.\n\nSaludos cordiales" │
│ {2} = Order Total │ FR: "Cher/Chère {0},\n\nMerci pour votre commande #{1}.\n │
│ │ Le total de votre commande est {2}.\n\nCordialement" │
├─────────────────────────┼────────────────────────────────────────────────────────────────┤
│ ApprovalRequiredSubject │ EN: "Approval Required: {0}" │
│ Placeholders: {0} │ ES: "Aprobación Requerida: {0}" │
│ = Request Type │ FR: "Approbation Requise: {0}" │
└─────────────────────────┴────────────────────────────────────────────────────────────────┘
Best Practices
Organize by Functional Area: Create separate Localized Resources for different purposes (ValidationMessages, EmailTemplates, StatusLabels, ReportText) rather than one giant resource.
Use Consistent Key Naming: Establish a naming convention for keys. For example: {Area}_{Action}_{Detail} like "Order_Validation_AmountExceeded" or simply descriptive names like "AmountExceeded".
Document Placeholders: Always document what each placeholder represents. Someone translating the strings needs to understand what 0, 1, 2 mean to create grammatically correct translations.
Consider Grammar Differences: Different languages have different grammar structures. Placeholder order might feel natural in English but awkward in other languages. Keep placeholders flexible and test with native speakers.
Plan for Text Expansion: Translations are often longer than the original English text. German text, for example, can be 30% longer than English. Ensure your UI and documents can accommodate longer strings.
Maintain Consistency: Use the same key for the same message everywhere. Don't create "RequiredField", "FieldRequired", and "MandatoryField" for the same message - this creates unnecessary translation work and inconsistency.
Provide Every Language You Support: If a resource has no value for the resolved language, an empty string is returned - there is no automatic fallback to another language. Make sure each resource carries a value for every language your users actually use, and add the translation for a new language before rolling it out.