Skip to main content

Version: 25.04

Locales

Internationalization (i18n) appears to be simple but can be complex. Some considerations are:

  • Front-end internationalization
  • Internationalization of server
  • Internationalized resource file management
  • How to collaborate between projects, developers and translators
  • Internationalization of dates and times

Internationalization implementations are often tied to specific technology stacks. This internationalization setup is only for the React technology stack and does not involve server-side internationalization.

Front-end internationalization

The core steps of internationalization are as follows:

  1. Add the desired locale to the transact-config configuration. If omitted, the default locale is en.

  2. Create and import a messages file, stored as a key-value object in the src/locales folder.

  3. Import and re-export those locale message files in src/locales/index.ts. For example:

    src/locales/index.ts
    import en from './en';
    import es from './es';
    import pt from './pt';
    import zhCN from './zh-cn';
    import zhTW from './zh-tw';

    export default {
    en,
    es,
    pt,
    'zh-CN': zhCN,
    'zh-TW': zhTW,
    };
    note

    Some locales include a region code as well as the language code, such as Chinese locales like zh-CN and zh-TW. The sample src/locales/index.ts file above shows how to add locales with a region code. For example, for the Chinese locale zh-CN, add 'zh-CN': zhCN to the exported locales.

  4. Pass the configs and messages to the App init.

The Workspaces app allows the user to select a language for static text in the UI. The list of languages offered is generated by matching the exported locales against the following list.

{value: 'en', id: 'eng', key: 'English',label: 'English'},
{value: 'ar', id: 'ara', key: 'Arabic',label: 'Arabic'},
{value: 'af', id: 'afr', key: 'Afrikaans locale',label: 'Afrikaans'},
{value: 'ar-DZ', id: 'ara', key: 'Arabic locale (Algerian Arabic)',label: 'Algerian Arabic'},
{value: 'ar-MA', id: 'ara', key: 'Arabic locale (Moroccan Arabic)',label: 'Moroccan Arabic'},
{value: 'ar-SA', id: 'ara', key: 'Arabic locale (Sauid Arabic)',label: 'Arabic'},
{value: 'ar-TN', id: 'ara', key: 'Arabic locale (Tunisian Arabic)',label: 'Arabic'},
{value: 'az', id: 'aze', key: 'Azerbaijani locale',label: 'Azerbaijani'},
{value: 'be', id: 'bel', key: 'Belarusian locale',label: 'Belarusian'},
{value: 'bg', id: 'bul', key: 'Bulgarian locale',label: 'Bulgarian'},
{value: 'bn', id: 'ben', key: 'Bengali locale',label: 'Bengali'},
{value: 'bs', id: 'bos', key: 'Bosnian locale',label: 'Bosnian'},
{value: 'ca', id: 'cat', key: 'Catalan locale',label: 'Catalan'},
{value: 'cs', id: 'ces', key: 'Czech locale',label: 'Czech'},
{value: 'cy', id: 'cym', key: 'Welsh locale',label: 'Welsh'},
{value: 'da', id: 'dan', key: 'Danish locale',label: 'Danish'},
{value: 'de', id: 'deu', key: 'German locale',label: 'German'},
{value: 'de-AT', id: 'deu', key: 'German locale (Austria)',label: 'German'},
{value: 'el', id: 'ell', key: 'Greek locale',label: 'Greek'},
{value: 'en-AU', id: 'eng', key: 'English locale (Australia)',label: 'English'},
{value: 'en-CA', id: 'eng', key: 'English locale (Canada)',label: 'English'},
{value: 'en-GB', id: 'eng', key: 'English locale (United Kingdom)',label: 'English'},
{value: 'en-IN', id: 'eng', key: 'English locale (India)',label: 'English'},
{value: 'en-NZ', id: 'eng', key: 'English locale (New Zealand)',label: 'English'},
{value: 'en-US', id: 'eng', key: 'English locale (United States)',label: 'English'},
{value: 'en-ZA', id: 'eng', key: 'English locale (South Africa)',label: 'English'},
{value: 'eo', id: 'epo', key: 'Esperanto locale',label: 'Esperanto'},
{value: 'es', id: 'spa', key: 'Spanish locale',label: 'Spanish'},
{value: 'et', id: 'est', key: 'Estonian locale',label: 'Estonian'},
{value: 'eu', id: 'eus', key: 'Basque locale',label: 'Basque'},
{value: 'fa-IR', id: 'ira', key: 'Persian/Farsi locale (Iran)',label: 'Persian'},
{value: 'fi', id: 'fin', key: 'Finnish locale',label: 'Finnish'},
{value: 'fr', id: 'fra', key: 'French locale',label: 'French'},
{value: 'fr-CA', id: 'fra', key: 'French locale (Canada)',label: 'French'},
{value: 'fr-CH', id: 'fra', key: 'French locale',label: 'French'},
{value: 'gd', id: 'gla', key: 'Scottish Gaelic',label: 'Scottish Gaelic'},
{value: 'gl', id: 'glg', key: 'Galician locale',label: 'Galician'},
{value: 'gu', id: 'guj', key: 'Gujarati locale (India)',label: 'Gujarati'},
{value: 'he', id: 'heb', key: 'Hebrew locale',label: 'Hebrew'},
{value: 'hi', id: 'hin', key: 'Hindi locale (India)',label: 'Hindi'},
{value: 'hr', id: 'hrv', key: 'Croatian locale',label: 'Croatian'},
{value: 'ht', id: 'hat', key: 'Haitian Creole locale',label: 'Haitian Creole'},
{value: 'hu', id: 'hun', key: 'Hungarian locale',label: 'Hungarian'},
{value: 'hy', id: 'arm', key: 'Armenian locale',label: 'Armenian'},
{value: 'id', id: 'ind', key: 'Indonesian locale',label: 'Indonesian'},
{value: 'is', id: 'isl', key: 'Icelandic locale',label: 'Icelandic'},
{value: 'it', id: 'ita', key: 'Italian locale',label: 'Italian'},
{value: 'ja', id: 'jpn', key: 'Japanese locale',label: 'Japanese'},
{value: 'ja-Hira', id: 'jpn', key: 'Japanese (Hiragana) locale',label: 'Japanese (Hiragana)'},
{value: 'ka', id: 'geo', key: 'Georgian locale',label: 'Georgian'},
{value: 'kk', id: 'kaz', key: 'Kazakh locale',label: 'Kazakh'},
{value: 'kn', id: 'kan', key: 'Kannada locale (India)',label: 'Kannada'},
{value: 'ko', id: 'kor', key: 'Korean locale',label: 'Korean'},
{value: 'lb', id: 'ltz', key: 'Luxembourgish locale',label: 'Luxembourgish'},
{value: 'lt', id: 'lit', key: 'Lithuanian locale',label: 'Lithuanian'},
{value: 'lv', id: 'lav', key: 'Latvian locale (Latvia)',label: 'Latvian'},
{value: 'mk', id: 'mkd', key: 'Macedonian locale',label: 'Macedonian'},
{value: 'mn', id: 'mon', key: 'Mongolian locale',label: 'Mongolian'},
{value: 'ms', id: 'msa', key: 'Malay locale',label: 'Malay'},
{value: 'mt', id: 'mlt', key: 'Maltese locale',label: 'Maltese'},
{value: 'nb', id: 'nob', key: 'Norwegian Bokmål locale',label: 'Norwegian Bokmål'},
{value: 'nl', id: 'nld', key: 'Dutch locale',label: 'Dutch'},
{value: 'nl-BE', id: 'nld', key: 'Dutch locale',label: 'Dutch'},
{value: 'nn', id: 'nno', key: 'Norwegian Nynorsk locale',label: 'Norwegian Nynorsk'},
{value: 'pl', id: 'pol', key: 'Polish locale',label: 'Polish'},
{value: 'pt', id: 'por', key: 'Portuguese locale',label: 'Portuguese'},
{value: 'pt-BR', id: 'por', key: 'Portuguese locale (Brazil)',label: 'Portuguese'},
{value: 'ro', id: 'ron', key: 'Romanian locale',label: 'Romanian'},
{value: 'ru', id: 'rus', key: 'Russian locale',label: 'Russian'},
{value: 'sk', id: 'slk', key: 'Slovak locale',label: 'Slovak'},
{value: 'sl', id: 'slv', key: 'Slovenian locale',label: 'Slovenian'},
{value: 'sq', id: 'sqi', key: 'Albanian locale',label: 'Shqip'},
{value: 'sr', id: 'srp', key: 'Serbian cyrillic locale',label: 'Serbian'},
{value: 'sr-Latn', id: 'srp', key: 'Serbian latin locale',label: 'Serbian'},
{value: 'sv', id: 'swe', key: 'Swedish locale',label: 'Swedish'},
{value: 'ta', id: 'tam', key: 'Tamil locale (India)',label: 'Tamil'},
{value: 'te', id: 'tel', key: 'Telugu locale',label: 'Telugu'},
{value: 'th', id: 'tha', key: 'Thai locale',label: 'Thai'},
{value: 'tr', id: 'tur', key: 'Turkish locale',label: 'Turkish'},
{value: 'ug', id: 'uig', key: 'Uighur locale',label: 'Uighur'},
{value: 'uk', id: 'ukr', key: 'Ukrainian locale',label: 'Ukrainian'},
{value: 'uz', id: 'uzb', key: 'Uzbek locale',label: 'Uzbek'},
{value: 'vi', id: 'vie', key: 'Vietnamese locale (Vietnam)',label: 'Vietnamese'},
{value: 'zh-CN', id: 'zho', key: 'Chinese Simplified locale',label: 'Chinese Simplified'},
{value: 'zh-TW', id: 'zho', key: 'Chinese Traditional locale',label: 'Chinese Traditional'},

Date translations use the date-fns library which requires some unique locale data when doing internationalization, mainly for relative time translation such as yesterday, today, tomorrow, N minutes ago, or N months ago. Consequently, it's important to import the correct locale at the beginning of the file. To learn more, see Internationalization in the date-fns documentation.

Internationalized resource file management

The above document discusses the steps of how to internationalize after the introduction of messages files. Next, let's talk about the management of international resource files.

Currently we manage the resource file in the src/locales folder:

.
├── en.ts
├── es.ts
└── pt.ts

\*.ts is a resource file that returns an object; the key for our translation id, and the value for the specific language translation. For example:

src/locales/en.ts
export default {
Home: 'Home',
All: 'All',
Apply: 'Apply',
Process: 'Process',
Helpdesk: 'Helpdesk',
...
}
src/locales/es.ts
export default {
Home: 'Hogar',
All: 'Todos',
Applications: 'Aplicaciones',
Review: 'Revisar',
Funding: 'Fondos',
...
}

Right-to-left languages

note

Workspaces supports the following right-to-left languages based on the list included in the article Right-To-Left vs Left-To-Right. If any issues are discovered with this behaviour or another ISO language code needs to be added, you can contact us to discuss your requirements.

If the locale specified in your transact-config matches one of the ISO language codes listed below, the application will display in right-to-left mode. For more information about locales, see Locales.

ISO Language CodeLanguage Name
arArabic
arcAramaic
dvDivehi
faPersian
haHausa
heHebrew
khwKhowar
ksKashmiri
kuKurdish
psPashto
urUrdu
yiYiddish

Translation Namespacing

Workspaces enables translation of hardcoded values and values from API responses in TJM. You can use the same values as keys for translations. Starting with version 25.04, namespace keys were introduced to provide more granular control over translations.

In your language translation files, you can define a global translation by using values as keys. These translations will apply wherever the word or phrase appears in the app. Additionally, you can override global translations for specific sections or components using namespace keys.

The table below maps the parent namespace keys available in the app to their corresponding sections or components.

KeySection/Component
app.actionsAction Buttons
app.attachmentsAttachments/Documents Panel
app.autocompleteAutocomplete Dropdown
app.bulkactionBulk Actions
app.configeditorConfig Editor
app.customcardsCustom Cards
app.dashboardlayoutApp Header Bar
app.datepickerDate Picker
app.daterangepickerDate Range Picker
app.errorpageError Page
app.jobswitcherApplication Switcher (Mobile)
app.sidesheetSideSheet
app.notesNotes Panel
app.notificationsNotifications Bar
app.pdfRendererPDF Renderer
app.refreshcontrolRefresh Control
app.listingListing Table
app.searchListing Table Search
app.searchFilterListing Table Filter
app.timelineTimeline
app.treecardTree
app.txnswitcherTransaction Switcher

Example

In this example, the phrase Show All is used globally in the app and will render as Show All unless overridden. For the app.txnswitcher namespace, the override app.txnswitcher.showAll changes the rendering to Show All Tasks specifically in the Transaction Switcher component. When the Spanish language is selected, the override renders as Mostrar todo.

// ./locales/en.ts
export default {
'Mark all as Read': 'Mark all as Read',
'No messages to display': 'No messages to display',
'New comment at': 'New comment at',
'New assignment at': 'New assignment at',
'Assigned by': 'Assigned by',
'Show All': 'Show All' // global translation
'app.txnswitcher.showAll': 'Show All Tasks' // namespace override
};
// ./locales/es.ts - Spanish
export default {
'New comment at': 'Nuevo comentario en',
'New assignment at': 'Nueva asignación en',
'Assigned by': 'Asignado por',
'Failed to load PDF file': 'No se pudo cargar el archivo PDF',
'app.txnswitcher.showAll': 'Mostrar todo', // namespace override
};

Reference

An auto-generated reference to default translation keys is provided below. This reference serves as a guide for all available namespace keys that can be overridden.

{
"app.actions.label": "Actions",
"app.actions.noaction": "No actions available",
"app.attachments.nodownload": "No download available",
"app.attachments.upload": "Upload",
"app.autocomplete.choose": "Choose an option",
"app.bulkaction.apply": "Apply",
"app.bulkaction.bulkactions": "Bulk Actions",
"app.bulkaction.selected": "selected",
"app.chat.noagent": "Sorry, no agent is currently available",
"app.configeditor.configure": "Configure",
"app.configeditor.download": "Download",
"app.configeditor.preview": "Preview",
"app.configeditor.save": "Save",
"app.configeditor.ui": "UI",
"app.configeditor.upload": "Upload",
"app.customcards.action": "Action",
"app.customcards.copy": "Copy Text",
"app.customcards.legend": "Legend",
"app.customcards.negative": "Negative",
"app.customcards.neutral": "Neutral",
"app.customcards.nocards": "There are no Custom Cards to view",
"app.customcards.norecords": "No records found",
"app.customcards.positive": "Positive",
"app.customcards.reset": "Reset",
"app.customcards.select": "Select",
"app.customcards.submit": "Submit",
"app.customcards.warning": "Warning",
"app.dashboardlayout.applicationid": "Application ID",
"app.dashboardlayout.documents": "Documents",
"app.dashboardlayout.logout": "Logout",
"app.dashboardlayout.notes": "Notes",
"app.dashboardlayout.outofoffice": "Out of Office",
"app.datepicker.cancel": "Cancel",
"app.datepicker.clear": "Clear",
"app.datepicker.ok": "OK",
"app.datepicker.pickadate": "Pick a Date",
"app.daterangepicker.cancel": "Cancel",
"app.daterangepicker.clear": "Clear",
"app.daterangepicker.ok": "OK",
"app.daterangepicker.pickadate": "Pick a Date",
"app.dropdown.cancel": "Cancel",
"app.dropdown.confirm": "Confirm",
"app.errorpage.logout": "Logout",
"app.errorpage.sorry": "Sorry",
"app.errorpage.support": "Please contact support.",
"app.errorpage.wrong": "Something went wrong.",
"app.jobswitcher.applications": "Applications",
"app.jobswitcher.updated": "Updated:",
"app.listing.cardsperpage": "Cards per page",
"app.listing.details": "Go to Detail Screen",
"app.listing.gotodetails": "Go to Detail Screen",
"app.listing.morelist": "More Information",
"app.listing.moretable": "Transactions",
"app.listing.nodata": "No Data",
"app.listing.rowsperpage": "Rows per page",
"app.listing.sort": "Sort",
"app.notes.addnote": "Add note",
"app.notes.addnotes": "Add note",
"app.notes.comment": "Comment",
"app.notes.comments": "Comments",
"app.notes.showall": "Show All",
"app.notes.showless": "Show Less",
"app.notes.viewthread": "View Thread",
"app.notifcations.all": "All",
"app.notifcations.markallasread": "Mark all as Read",
"app.notifcations.markasread": "Mark as Read",
"app.notifcations.newassigmentat": "New assignment at",
"app.notifcations.newcommentat": "New comment at",
"app.notifcations.nomessagetodisplay": "No messages to display",
"app.notifcations.open": "Open",
"app.notifcations.readopened": "Read/Opened",
"app.notifcations.thread": "Unread",
"app.notifications.assignedby": "Assigned by",
"app.notifications.newassigmentat": "New assignment",
"app.pagination.next": "Next",
"app.pagination.pageInfo": "Page",
"app.pagination.previous": "Previous",
"app.pdfRenderer.failedToLoadPdfFile": "Failed to load PDF file",
"app.pdfrenderer.notauthorized": "Not authorised",
"app.refreshcontrol.updated": "Updated",
"app.search.clear": "Clear",
"app.search.type": "Type exact text to match; e.g. \"Apple\" instead of \"Appl\"",
"app.searchFilter.apply": "Apply",
"app.searchFilter.globalfilters": "Global Filters",
"app.searchFilter.title": "Search Filters",
"app.searchFilter.viewfilter": "View Filters",
"app.searchfilter.all": "All",
"app.searchfilter.apply": "Apply",
"app.searchfilter.filter": "Filter",
"app.searchfilter.filters": "Filters",
"app.searchfilter.reset": "Reset",
"app.select.all": "All",
"app.select.none": "None",
"app.sidesheet.close": "Close",
"app.sidesheet.noreceipt": "No receipt URL found",
"app.table.loading": "Loading",
"app.table.sort": "Sort",
"app.timeline.noresult": "No result",
"app.timeline.step": "Step",
"app.timeline.tasks": "Tasks",
"app.treecard.loading": "Loading...",
"app.treecard.reset": "Reset",
"app.treecard.zoomin": "Zoom in",
"app.treecard.zoomout": "Zoom out",
"app.txnswitcher.showAll": "Show All",
"app.txnswitcher.showLess": "Show Less",
"app.txnswticher.status": "Status",
"app.txnswticher.taskId": "Task ID",
"app.txnswticher.tasks": "Tasks"
}