Localized Resource
A Localized Resource defines multi-language strings that can be dynamically retrieved based on the user's language preference. 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. FlowOn Logic automatically returns the appropriate translation based on the current user's language setting in Dynamics 365.
Structure
A Localized Resource consists of:
| Component | Description |
|---|---|
| Name | Unique identifier for the resource (e.g., "ValidationMessages", "EmailTemplates") |
| Description | Documentation explaining what strings this resource contains |
| Project | The FlowOn Project this resource belongs to |
| Default Language | The fallback language if a translation is not available |
| Supported Languages | List of languages with translations (English, Spanish, French, German, etc.) |
| String Entries | The individual strings with their keys and translations |
String Entries
Each string entry within a Localized Resource has:
| Property | Description |
|---|---|
| Key/Index | Unique identifier for this string (e.g., "RequiredField", "OrderApproved") |
| Translations | The text in each supported language |
| Description | Documentation explaining when/where this string is used |
| Placeholders | Documentation of any dynamic placeholders in the string |
Placeholders for Dynamic Content
Strings often need to include dynamic values—an amount, a name, a date. Localized Resources support placeholders that are replaced with actual values at runtime:
| 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" |
When the logic executes, it provides the actual values: GetLocalizedString("AmountExceeded", "$15,000", "$10,000") returns "Amount $15,000 exceeds limit of $10,000" (or the equivalent in the user's language).
Language Resolution
When a Localized Resource string is requested, FlowOn Logic resolves the language in this order:
- User's Language Setting: The language configured in the user's Dynamics 365 personal options
- Organization Default: The organization's base language setting
- Resource Default: The default language specified in the Localized Resource
If a translation doesn't exist for the resolved language, the system falls back to the Resource Default language. This ensures users always see something meaningful, even if a specific translation is missing.
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
Localized Resource: ValidationMessages
Description: "Error messages displayed when data validation fails"
Default Language: English
Supported Languages: English, Spanish, French, German, Portuguese
String Entries:
┌────────────────────┬────────────────────────────────────────────────────────────────────┐
│ 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
Localized Resource: EmailTemplates
Description: "Email subject lines and body content for automated notifications"
Default Language: English
Supported Languages: English, Spanish, French
String Entries:
┌─────────────────────────┬────────────────────────────────────────────────────────────────┐
│ 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.
Handle Missing Translations Gracefully: Always define strings in the default language. If a new string is added but not yet translated, users will see the default language version rather than an error.