Skip to main content

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:

  1. Define strings once with a unique key/index
  2. Provide translations for each supported language
  3. Reference by key in your Logic Blocks and Flows
  4. 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:

ComponentDescription
NameUnique identifier for the resource - and the key you use to look it up (e.g., "RequiredField", "OrderApproved")
DescriptionDocumentation explaining what this string is for
ProjectThe Flowon Project this resource belongs to
Language ValuesOne translated string per language, each keyed by that language's LCID (locale ID, an integer - e.g., 1033 for English, 1036 for French)
One resource = one string

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:

PropertyDescription
Language (LCID)The integer locale ID identifying the language (e.g., 1033 English, 3082 Spanish, 1036 French, 1031 German)
ValueThe 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:

PlaceholderDescription
{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:

KeyEnglishSpanishFrench
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.

ValidationMessagesLocalized Resource
Default: ENAUTO
USER LANGUAGE
English
KEYRESOLVED STRING · ENGLISHPLACEHOLDERS
RequiredField"This field is required" -
InvalidEmail"Please enter a valid email address" -
AmountExceeded"Amount exceeds the maximum limit of {0}"{0} = Max Amount
DateInPast"Date cannot be in the past" -
InvalidRange"{0} must be between {1} and {2}"{0} = Field{1} = Min{2} = Max
PLACEHOLDER RESOLUTION · AmountExceeded
Template"Amount exceeds the maximum limit of {0}"
substituting {0}$10,000
Resolved"Amount exceeds the maximum limit of $10,000"

Language Resolution

When a Localized Resource string is requested, the language is resolved as follows:

  1. Explicit language: If the request specifies a language (an LCID), that language is used.
  2. 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.