skills/internationalization/SKILL.md
Complete guide to internationalizing a Cumulocity Web SDK application. Covers all approaches to annotating and translating text (gettext, translate pipe, translate directive, TranslateService), extracting strings, creating and updating .po files, overriding existing translations, and adding brand-new languages. Triggers: i18n, internationalization, add language, translate, translation, localization, l10n, new language, po file, gettext, TranslateService, language switcher.
npx skillsauth add cumulocity-iot/cumulocity-skills internationalizationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Cumulocity Web SDK uses the gettext .po file
format for translations. There are two distinct problems to solve:
.po files and wiring them into the appThis skill covers both, plus the complete workflow for adding a brand-new language.
The @c8y/ngx-components package ships ready-made .po files for these locales:
| Code | Language |
|---|---|
| de | German |
| en | English |
| en_US | English (US) |
| es | Spanish |
| fr | French |
| ja_JP | Japanese |
| ko | Korean |
| nl | Dutch |
| pl | Polish |
| pt_BR | Brazilian Portuguese |
| zh_CN | Simplified Chinese |
| zh_TW | Traditional Chinese |
To activate any of these, only Steps 5 and 6 of the "Adding a New Language" section below are required — no translation work needed.
Four mechanisms are available. They can be combined freely.
gettext(string)Annotates a static string literal for extraction. Does not translate at runtime —
use it to mark strings defined in TypeScript that are translated elsewhere (e.g. via pipe
or TranslateService).
import { gettext } from '@c8y/ngx-components';
const label = gettext('Remove Device');
Rules:
- Must be a string literal — not a variable or template literal.
- ✅
gettext('Remove Device')- ❌
gettext(myVar)- ❌
gettext(`Remove ${condition ? 'Device' : 'Group'}`)
translate PipeTranslates the string at runtime inside templates. When applied to a static string
literal it also annotates it for extraction — no separate gettext() call needed.
<!-- Simple -->
{{ 'Device' | translate }}
<!-- With interpolated placeholders — use @let to pre-resolve -->
@let removeLabel = 'Remove Device' | translate;
@let cancelLabel = 'Cancel' | translate;
{{ confirmed ? removeLabel : cancelLabel }}
<!-- Or expose gettext on the component class and use it inline -->
{{ (confirmed ? gettext('Remove Device') : gettext('Cancel')) | translate }}
Note: Text extraction from the
translatepipe does not work inside complex template expressions. Use@let,gettext(), or define labels in the component class.
translate DirectiveTranslates the element's text content in place. Also annotates it for extraction.
<span translate>Device</span>
<!-- Interpolated placeholders: add ngNonBindable to prevent Angular from
processing {{ }} before the translation module can handle them -->
<span translate ngNonBindable>{{ filteredCount }} of {{ total }} items.</span>
TranslateServiceUse for imperative translation inside TypeScript. The string must be separately
annotated with gettext().
import { TranslateService } from '@ngx-translate/core';
import { gettext } from '@c8y/ngx-components';
@Injectable()
export class MyService {
constructor(private translate: TranslateService) {}
getLabel(): string {
const key = gettext('Remove Device'); // annotate for extraction
return this.translate.instant(key); // translate at runtime
}
}
Append context in backticks inside the string to guide translators (and AI translation tools). Context is stripped at runtime — users never see it.
gettext('Next`page`')
gettext('Set as latest`version`')
gettext('Cover`verb, image fitting option`')
Special context KEEP_ORIGINAL marks a string as intentionally untranslated:
gettext('MyBrand`KEEP_ORIGINAL`')
locales.potRun this command from the project root to extract all annotated strings into
./locales/locales.pot:
ng extract-i18n
The .pot file is the master template used to create or update per-language .po
files. Re-run this command every time strings are added or changed.
.po Files.pot file../src/locales/<lang>.po.# Create a new .po from a .pot
msginit --input=./locales/locales.pot --locale=fr --output=./src/locales/fr.po
# Update an existing .po with new/changed strings from .pot
msgmerge --update ./src/locales/fr.po ./locales/locales.pot
.po file structuremsgid ""
msgstr ""
"Project-Id-Version: c8yui.core\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: fr\n"
msgid "Remove Device"
msgstr "Supprimer l'appareil"
msgid "{{ filteredCount }} of {{ total }} items."
msgstr "{{ filteredCount }} sur {{ total }} éléments."
The
msgidmust exactly match the source string (including any backtick context if present).
To change a translation that already exists in a built-in language pack (e.g. change German "Geräte" → "Maschinen"):
./src/locales/de.po with only the entries you want to override:msgid ""
msgstr ""
"Project-Id-Version: c8yui.core\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: de\n"
msgid "Devices"
msgstr "Maschinen"
./src/i18n.ts:import '@c8y/ngx-components/locales/de.po'; // built-in (base)
import './locales/de.po'; // your overrides (wins)
.po Files into the App (i18n.ts)Every .po file — built-in or custom — must be imported in ./src/i18n.ts:
// Built-in language packs from the framework
import '@c8y/ngx-components/locales/de.po';
import '@c8y/ngx-components/locales/en.po';
import '@c8y/ngx-components/locales/fr.po';
// … add more as needed
// Custom / override translations
import './locales/fr.po'; // your custom French strings
import './locales/it.po'; // Italian (new language)
Import order matters for overrides: the last import for a given
msgidwins.
Use this when the language is not in the built-in list (e.g. Italian, Arabic, Hindi).
.potmkdir -p ./locales
curl -o ./locales/framework.pot https://unpkg.com/@c8y/ngx-components@latest/locales/locales.pot
# For a specific version (>= 1004.0.6):
curl -o ./locales/framework.pot https://unpkg.com/@c8y/[email protected]/locales/locales.pot
ng extract-i18n
# Output: ./locales/locales.pot
.pot filesmsgcat -o ./locales/merged.pot ./locales/framework.pot ./locales/locales.pot
.po file for the new languagemsginit --input=./locales/merged.pot --locale=it --output=./src/locales/it.po
# Then open in Poedit or a text editor and fill in the msgstr values
.po file in i18n.ts// src/i18n.ts
import './locales/it.po';
cumulocity.config.tsWithout this step the language will not appear in the language switcher, even if the
.po file is imported.
// cumulocity.config.ts
export default {
runTime: {
languages: {
it: {
name: 'Italian',
nativeName: 'Italiano'
}
}
}
};
ng serve -u https://<yourTenant>.cumulocity.com/
Open the app → User menu → Language picker. The new language should be listed. Switch to it and confirm strings render correctly.
{{ myDate | c8yDate }}
{{ myDate | date:'fullDate' }}
The Angular date pipe automatically uses the active locale set by the translation
module — no extra configuration needed.
DynamicFormsModule has i18n built in. Simply annotate the label strings in the schema
definition with gettext() and they will be extracted and translated automatically:
import { gettext } from '@c8y/ngx-components';
const schema = {
properties: {
name: {
title: gettext('Device name'),
type: 'string'
}
}
};
| Pitfall | Fix |
|---|---|
| Language imported in i18n.ts but not in cumulocity.config.ts languages | Add the language declaration to runTime.languages — both steps are required |
| gettext() called with a variable or template literal | Must be a static string literal |
| translate pipe used inside a complex ternary — strings not extracted | Use @let pre-translation or gettext() on the component class |
| Interpolated {{ }} in translated template consumed by Angular before the translation module | Add ngNonBindable to the element |
| Override .po imported before the built-in pack | Import overrides after the base pack |
| msgid in override .po doesn't match exactly | Run ng extract-i18n and look up the exact source string in the generated .pot |
| Adding a new language for a framework-based app without the merged .pot | Download the framework .pot and merge with msgcat before translating |
gettext(), translate pipe, or translate directiveng extract-i18n run and locales.pot is up to date.po files created/updated for every target language.po files imported in src/i18n.tscumulocity.config.ts → runTime.languages| Resource | URL |
|---|---|
| Cumulocity i18n Codex docs | https://cumulocity.com/codex/components/application-and-system/internationalization |
| Tutorial app translation examples | https://github.com/Cumulocity-IoT/tutorial/tree/main/src/translations |
| Framework .pot master template | https://unpkg.com/@c8y/ngx-components@latest/locales/locales.pot |
| Poedit (translation editor) | https://poedit.net |
| GNU gettext tools | https://www.gnu.org/software/gettext/ |
tools
Scaffold a new Cumulocity application using the @c8y/websdk Angular schematic without human interaction. Covers Angular CLI installation, app generation, schematic setup, AI tools configuration, dev server, and build commands. Triggers: new app, scaffold, create application, ng add websdk, setup cumulocity app, new cumulocity project.
tools
Step-by-step guide to migrate a Cumulocity Web SDK application to a target version. Detects breaking changes with the ui-breaking-changes-cli, scaffolds a reference app at the target version with the new-app skill, compares key configuration files (app.ts, bootstrap.ts, angular.json, etc.), and finishes with a code-quality-analysis review. Triggers: migrate app, upgrade version, breaking changes, sdk upgrade, migrate cumulocity, upgrade websdk.
development
Analyze Angular / Cumulocity Web SDK code for anti-patterns, bugs, and quality issues. Use when reviewing components, services, or modules for code quality, maintainability, performance, and correctness. Covers TypeScript best practices, Angular idioms, C8Y SDK usage patterns, and project-specific conventions. Triggers: code review, anti-pattern, quality check, refactor suggestion, style guide, bug analysis.
development
Recommends and support the best approach for migrating PKI certificates when moving from another platform or PKI to Cumulocity. Use when a developer or architect asks about how to migrate existing PKI certificates to Cumulocity, how to handle certificate rotation during migration, or best practices for PKI management in Cumulocity.