Implementing Internationalization in SAP CAP: Multi-Language Support Using i18n Properties
Share

[[{“value”:”

Introduction

Enterprise applications rarely serve users from a single country. A Fiori app built in Bengaluru may well be used by someone in Paris or Frankfurt, and showing them English column headers everywhere isn’t a great experience.

Internationalization (i18n) solves this. Instead of hardcoding texts such as Name, City, or General Information directly in CDS annotations, we externalize them into language-specific properties files. At runtime, the application loads the appropriate language-specific texts, allowing the UI to change languages without modifying the underlying application logic.

In this blog, I’ll build a small CAP application with a Fiori Elements UI and demonstrate English and French translations using i18n properties files. The same approach can be extended to additional languages such as German, Hindi, and Kannada.

What actually gets translated

One thing confused me when I first set this up, so let me get it out of the way early: there are two separate i18n layers in a CAP + Fiori Elements project, and they sit in different folders.

The first is the CAP layer, a folder named _i18n at the project root. This is what translates the labels in your CDS annotations: field labels, facet headers, that kind of thing. The CAP layer is responsible for resolving i18n keys used in CDS annotations, such as field labels and facet titles, before the corresponding OData metadata is consumed by the Fiori Elements application.

The second is the UI5 layer, the i18n folder inside webapp. This handles the app title, description, and any texts you reference directly from your UI5 views or controllers. It’s resolved in the browser by the standard UI5 resource model.

So when your List Report shows Nom instead of Name, that swap happened on the CAP server. The app title in the launchpad tile, on the other hand, gets translated by UI5. You need both working correctly for a fully translated app, and it’s easy to spend an hour debugging the wrong one.

Neither layer touches the actual business data, by the way. If a row in your database says Bangalore, it stays Bangalore no matter what language the UI is in. Translating the data itself is a different CAP feature (localized data, using .texts entities) and isn’t something I’m getting into here.

Prerequisites

  • Node.js installed
  • `@sap/cds-dk` installed globally
  • VS Code or SAP Business Application Studio
  • Basic familiarity with CAP and Fiori Elements

Step 1: Define the data model

Create db/schema.cds with a simple students entity:

namespace db;

entity students {
    key id   : String;
        name : String;
        age  : Integer;
        city : String;
}

Nothing fancy. Four fields is enough to demonstrate the translation part, which is really what this post is about.

Step 2: Expose it as a service

Create srv/service.cds:

using { db.students } from '../db/schema';

service MyService {
    entity entity1 as projection on students;
}

This gives us an OData V4 service at /odata/v4/my/ with a single entity set.

Step 3: Add sample data

CAP can generate CSV sample files for you:

cds add data --records 10

This creates db/data/db-students.csv. I replaced the generated values with something more readable:

id,name,age,city
S001,Chetan,25,Gadag
S002,Rahul,24,Bangalore
S003,Akash,26,Mysore
S004,Priya,23,Hubli
S005,Anil,25,Dharwad

Then add SQLite as the local database and deploy:

npm add @cap-js/sqlite
cds deploy

Step 4: Generate the Fiori Elements app

Use the Fiori generator (`@sap/generator-fiori`to create a List Report Object Page application on top of MyService. Once that’s done, the project structure looks like this:

internationalization/
│
├── _i18n/                          <-- CAP layer: translates CDS annotations
│   ├── i18n.properties
│   ├── i18n_en.properties
│   ├── i18n_de.properties
│   ├── i18n_fr.properties
│   ├── i18n_hi.properties
│   └── i18n_kn.properties
│
├── app/
│   └── project1/
│       ├── webapp/
│       │   └── i18n/               <-- UI5 layer: app title, description
│       │       └── i18n.properties
│       ├── annotations.cds
│       ├── manifest.json
│       └── ...
│
├── db/
│   ├── data/
│   │   └── db-students.csv
│   └── schema.cds
│
├── srv/
│   └── service.cds
│
├── package.json
└── README.md

The _i18n folder at the project root is the recommended location for CAP text bundles. It allows the translations to be shared across the CAP project.

Step 5: Create the i18n properties files

This is where the actual translation lives. Create an _i18n folder at the project root and add the default file i18n.properties:

appTitle=Students Application
appDescription=Student information
uid=ID
name=Name
age=Age
city=City
generalInformation=General Information

Then create i18n_fr.properties with the same keys and French values:

appTitle=Application des étudiants
appDescription=Informations sur les étudiants
uid=ID
name=Nom
age=Âge
city=Ville
generalInformation=Informations générales

Every other language follows the exact same pattern: same keys, different values. I added i18n_en, i18n_de, i18n_hi, and i18n_kn alongside the French one, but since they’re just repeats of the structure above with translated strings, I won’t paste all of them here.

A few things worth keeping in mind while you do this. The naming convention is i18n_<locale>.properties: _fr for French, _de for German, _kn for Kannada. The plain i18n.properties, with no suffix, acts as the fallback. If a user’s browser is set to a language you haven’t translated, or a key is missing from their file, CAP quietly falls back to this one. And there’s no compile-time check tying your annotation keys to your properties file keys, so if you get a key name wrong you won’t see an error, you’ll just see the literal key name rendered in the UI.

The UI5 side gets its own, much smaller file at app/project1/webapp/i18n/i18n.properties, holding just appTitle and appDescription. Keeping the two sets of files apart isn’t an accident. The CAP files are read by the server, the UI5 file by the browser, and they’re resolved through completely different code paths.

Step 6: Use the keys in annotations

Open app/project1/annotations.cds and replace the hardcoded labels with i18n references using the {i18n>key} syntax:

using MyService as service from '../../srv/service';

annotate service.entity1 with @(
    UI.FieldGroup #GeneratedGroup : {
        $Type : 'UI.FieldGroupType',
        Data  : [
            { $Type : 'UI.DataField', Label : '{i18n>uid}',  Value : id   },
            { $Type : 'UI.DataField', Label : '{i18n>name}', Value : name },
            { $Type : 'UI.DataField', Label : '{i18n>age}',  Value : age  },
            { $Type : 'UI.DataField', Label : '{i18n>city}', Value : city }
        ]
    },
    UI.Facets : [
        {
            $Type  : 'UI.ReferenceFacet',
            ID     : 'GeneratedFacet1',
            Label  : '{i18n>generalInformation}',
            Target : '@UI.FieldGroup#GeneratedGroup'
        }
    ],
    UI.LineItem : [
        { $Type : 'UI.DataField', Label : '{i18n>uid}',  Value : id   },
        { $Type : 'UI.DataField', Label : '{i18n>name}', Value : name },
        { $Type : 'UI.DataField', Label : '{i18n>age}',  Value : age  },
        { $Type : 'UI.DataField', Label : '{i18n>city}', Value : city }
    ]
);

I also changed the facet label from the hardcoded 'General Information' to '{i18n>generalInformation}'. It’s easy to translate the column headers and forget the section titles, but users notice those just as much.

These keys are resolved against the root _i18n folder, nothing in webapp. When a request comes in, CAP checks the locale, picks the matching properties file, and returns a $metadata document with the labels already translated. You can actually see this happen by opening /odata/v4/my/$metadata?sap-language=fr directly in the browser. The French labels are sitting right there in the EDMX, before UI5 has done anything at all.

Step 7: Wire i18n into manifest.json

This part is purely UI5 territory. It has nothing to do with the annotation labels from the last step. The Fiori generator sets most of it up for you, but it’s worth knowing what’s actually happening.

sap.app points to the webapp bundle and uses double braces for the title and description:

“sap.app”: {
“id”: “project1”,
“type”: “application”,
“i18n”: “i18n/i18n.properties”,
“title”: “{{appTitle}}”,
“description”: “{{appDescription}}”
}

Note the syntax difference: {{key}} here in the descriptor, versus {i18n>key} everywhere else.

Under sap.ui5, two resource models get declared:

“models”: {
“i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“settings”: {
“bundleName”: “project1.i18n.i18n”
}
},
“@i18n”: {
“type”: “sap.ui.model.resource.ResourceModel”,
“uri”: “i18n/i18n.properties”
}
}

Both models belong to the UI5 application layer and point to the application’s webapp/i18n/i18n.properties bundle. They are separate from the CAP _i18n bundle used to resolve texts referenced from CDS annotations. The CDS annotation labels are resolved by CAP before the Fiori Elements application consumes the corresponding metadata.

Step 8: Run and test

Start the application:

npm run start

The app comes up in English first, since that’s the default:

Eng.png

English lang object page.png

Now switch the language. In Chrome, go to Settings → Languages, add French, and move it to the top of the list. Refresh the page.

chrome lang settings.png

french lang.png

french obj page.png

The column headers now read Nom, Âge, and Ville, and the app title has switched to Application des étudiants. The data hasn’t moved, Bangalore is still Bangalore, which is what you’d expect since this whole exercise was about translating labels, not data.

If you don’t want to keep flipping your browser’s language setting back and forth while testing, append ?sap-language=fr to the URL instead. It overrides the detected locale for that one session and is a lot faster during development.

Conclusion

In this blog, we saw how to add internationalization to an SAP CAP and Fiori Elements application using i18n properties files. By externalizing static UI texts and referencing them through i18n keys, the same application can display labels and titles in different languages without changing the underlying application logic.

The approach keeps translations separate from the application code, making it easier to maintain and extend the application with additional languages in the future.

With this setup in place, we can provide a more localized and user-friendly Fiori experience while keeping the business data unchanged.

 

“}]] 

  Read More Technology Blog Posts by Members articles 

#abap

By ali

Leave a Reply