The iTop Integration follows Nextcloud's localization system using the IL10N service. This document describes how translations work, how to add new languages, and the German informal/formal variant support.
Nextcloud uses a runtime translation system where:
- PHP backend uses
$this->l10n->t()for translations - Vue frontend uses
t('integration_itop', 'string') - Translation files are JSON in the
l10n/directory - Format:
{locale}.json(e.g.,de.json,de_DE.json,en.json)
Reference Implementation: This project follows the same l10n patterns as agenda_bot
Initial localization focuses on German with two variants:
| Locale | Variant | File | Usage |
|---|---|---|---|
de |
Informal (Du) | l10n/de.json |
Default German, casual/friendly tone |
de_DE |
Formal (Sie) | l10n/de_DE.json |
Formal German, business tone |
en |
English | l10n/en.json |
Default/fallback language |
Example Difference:
// de.json (informal)
{
"You can only view the agenda status and help.": "Du kannst nur den Agenda Status und die Hilfe einsehen."
}
// de_DE.json (formal)
{
"You can only view the agenda status and help.": "Sie können nur den Status der Tagesordnung und die Hilfe ansehen."
}Additional languages will be contributed by the community:
- French (
fr.json) - Spanish (
es.json) - Italian (
it.json) - Portuguese (
pt_BR.json) - Dutch (
nl.json) - Polish (
pl.json) - Russian (
ru.json) - Japanese (
ja.json) - Chinese (
zh_CN.json,zh_TW.json)
integration_itop/
├── l10n/
│ ├── de.json # German informal (Du)
│ ├── de_DE.json # German formal (Sie)
│ ├── en.json # English (fallback)
│ └── .gitkeep
├── lib/
│ └── */
│ └── *.php # PHP files with $this->l10n->t()
├── src/
│ └── */
│ └── *.vue # Vue files with t('integration_itop', ...)
└── docs/
└── l10n.md # This file
{
"translations": {
"English source string": "Translated string",
"String with placeholder %s": "Übersetzte Zeichenkette %s",
"String with number %d": "Zeichenkette mit Zahl %d",
"Plural %n item": {
"0": "Keine Elemente",
"1": "Ein Element",
"2+": "%n Elemente"
}
},
"pluralForm": "nplurals=2; plural=(n != 1);"
}Nextcloud l10n supports sprintf-style placeholders:
| Placeholder | Type | Example |
|---|---|---|
%s |
String | "User %s logged in" → "Benutzer %s angemeldet" |
%d |
Integer | "Found %d items" → "%d Elemente gefunden" |
%n |
Count (plural) | "%n ticket" → "%n Ticket" / "%n Tickets" |
PHP Usage:
// Simple string
$this->l10n->t('Configuration saved');
// With string placeholder
$this->l10n->t('Connected to %s', [$itopName]);
// With number placeholder
$this->l10n->t('Found %d CIs', [$count]);
// Plural form
$this->l10n->n('%n ticket', '%n tickets', $count);Vue Usage:
// Simple string
t('integration_itop', 'Configuration saved')
// With placeholder
t('integration_itop', 'Connected to {name}', {name: itopName})
// Plural form
n('integration_itop', '%n ticket', '%n tickets', count)Current translatable strings (25 total):
$this->l10n->t('Configuration removed successfully')
$this->l10n->t('Settings saved successfully')
$this->l10n->t('Token validation failed')
$this->l10n->t('Configuration successful! You are now connected.')
$this->l10n->t('Invalid URL format')
$this->l10n->t('User facing name is too long (max 100 characters)')
$this->l10n->t('Admin configuration saved')$this->l10n->t('iTop URL not configured')
$this->l10n->t('Application token not configured')
$this->l10n->t('User not configured')
$this->l10n->t('Bad credentials')
$this->l10n->t('Invalid JSON response from iTop')- Dashboard widget titles and descriptions
- Search result formatting
- Settings panel labels
- Reference provider labels
To be added in Phase 2:
- Form field labels
- Button text
- Validation messages
- Success/error notifications
- Form field labels
- Token validation messages
- User info display
- Help text
- Widget title
- Stat labels
- Empty state messages
- No results messages
- Loading states
- Error messages
Inject IL10N service:
use OCP\IL10N;
class MyController extends Controller {
public function __construct(
string $appName,
IRequest $request,
private IL10N $l10n // Inject IL10N
) {
parent::__construct($appName, $request);
}
public function someMethod() {
// Use $this->l10n->t()
return new DataResponse([
'message' => $this->l10n->t('Operation successful')
]);
}
}Translation best practices:
// ✅ Good - complete sentences
$this->l10n->t('Configuration saved successfully');
// ❌ Bad - fragments that don't translate well
$this->l10n->t('Configuration') . ' ' . $this->l10n->t('saved');
// ✅ Good - placeholders for dynamic content
$this->l10n->t('Connected to %s', [$instanceName]);
// ❌ Bad - concatenating translations
$this->l10n->t('Connected to') . ' ' . $instanceName;
// ✅ Good - context in the string
$this->l10n->t('Person ID not found - please configure your settings');
// ❌ Bad - ambiguous strings
$this->l10n->t('Not found');Import translation functions:
<script>
import { translate as t, translatePlural as n } from '@nextcloud/l10n'
export default {
name: 'MyComponent',
computed: {
title() {
return t('integration_itop', 'iTop Integration')
},
statusMessage() {
const count = this.tickets.length
return n('integration_itop', '%n open ticket', '%n open tickets', count)
}
}
}
</script>
<template>
<div>
<h2>{{ title }}</h2>
<p>{{ t('integration_itop', 'Configure your iTop connection below') }}</p>
<p>{{ statusMessage }}</p>
</div>
</template>Template usage:
<template>
<!-- Direct usage in templates -->
<NcButton>
{{ t('integration_itop', 'Save settings') }}
</NcButton>
<!-- With placeholders -->
<p>{{ t('integration_itop', 'Connected as {user}', {user: userName}) }}</p>
<!-- Plural forms -->
<span>{{ n('integration_itop', '%n CI found', '%n CIs found', ciCount) }}</span>
</template>Step 1: Extract all translatable strings
# List all t() calls in PHP
grep -r "\$this->l10n->t(" lib/ | sed "s/.*t('\([^']*\)'.*/\1/" | sort -u
# List all t() calls in Vue
grep -r "t('integration_itop'" src/ | sed "s/.*t('integration_itop', '\([^']*\)'.*/\1/" | sort -uStep 2: Create l10n/en.json (source language)
{
"translations": {
"Configuration saved successfully": "Configuration saved successfully",
"iTop URL not configured": "iTop URL not configured",
"Application token not configured": "Application token not configured",
"User not configured": "User not configured",
"Configuration successful! You are now connected.": "Configuration successful! You are now connected.",
"Invalid URL format": "Invalid URL format",
"Bad credentials": "Bad credentials",
"Invalid JSON response from iTop": "Invalid JSON response from iTop",
"Connected to %s": "Connected to %s",
"Found %d CIs": "Found %d CIs",
"%n open ticket": "%n open ticket",
"%n open tickets": "%n open tickets"
},
"pluralForm": "nplurals=2; plural=(n != 1);"
}Step 3: Create l10n/de.json (informal German)
{
"translations": {
"Configuration saved successfully": "Konfiguration erfolgreich gespeichert",
"iTop URL not configured": "iTop-URL nicht konfiguriert",
"Application token not configured": "Anwendungs-Token nicht konfiguriert",
"User not configured": "Benutzer nicht konfiguriert",
"Configuration successful! You are now connected.": "Konfiguration erfolgreich! Du bist jetzt verbunden.",
"Invalid URL format": "Ungültiges URL-Format",
"Bad credentials": "Ungültige Anmeldedaten",
"Invalid JSON response from iTop": "Ungültige JSON-Antwort von iTop",
"Connected to %s": "Verbunden mit %s",
"Found %d CIs": "%d CIs gefunden",
"%n open ticket": "%n offenes Ticket",
"%n open tickets": "%n offene Tickets"
},
"pluralForm": "nplurals=2; plural=(n != 1);"
}Step 4: Create l10n/de_DE.json (formal German)
{
"translations": {
"Configuration saved successfully": "Konfiguration erfolgreich gespeichert",
"iTop URL not configured": "iTop-URL nicht konfiguriert",
"Application token not configured": "Anwendungs-Token nicht konfiguriert",
"User not configured": "Benutzer nicht konfiguriert",
"Configuration successful! You are now connected.": "Konfiguration erfolgreich! Sie sind jetzt verbunden.",
"Invalid URL format": "Ungültiges URL-Format",
"Bad credentials": "Ungültige Anmeldedaten",
"Invalid JSON response from iTop": "Ungültige JSON-Antwort von iTop",
"Connected to %s": "Verbunden mit %s",
"Found %d CIs": "%d CIs gefunden",
"%n open ticket": "%n offenes Ticket",
"%n open tickets": "%n offene Tickets"
},
"pluralForm": "nplurals=2; plural=(n != 1);"
}Key Differences (Du vs Sie):
| Context | Informal (de.json) | Formal (de_DE.json) |
|---|---|---|
| You are connected | Du bist verbunden | Sie sind verbunden |
| Your settings | Deine Einstellungen | Ihre Einstellungen |
| You can configure | Du kannst konfigurieren | Sie können konfigurieren |
| Please check | Bitte überprüfe | Bitte überprüfen Sie |
Different languages have different plural rules. Nextcloud uses the GNU gettext plural form expressions.
| Language | nplurals | Plural Expression | Rule |
|---|---|---|---|
| English | 2 | (n != 1) |
1 item, 2+ items |
| German | 2 | (n != 1) |
1 Element, 2+ Elemente |
| French | 2 | (n > 1) |
0-1 item, 2+ items |
| Polish | 3 | (n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) |
Complex |
| Russian | 3 | (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2) |
1, 2-4, 5+ |
| Japanese | 1 | 0 |
No plurals |
| Arabic | 6 | Complex | 0, 1, 2, 3-10, 11-99, 100+ |
// English: "1 ticket" / "5 tickets"
$this->l10n->n('%n ticket', '%n tickets', $count);
// German: "1 Ticket" / "5 Tickets"
$this->l10n->n('%n Ticket', '%n Tickets', $count);
// Polish: "1 bilet" / "2 bilety" / "5 biletów"
$this->l10n->n('%n bilet', '%n bilety', $count);-
Write code with English strings
$message = $this->l10n->t('Configuration saved successfully');
-
Extract strings to en.json
- Run extraction script or manually collect strings
- Keep en.json as source of truth
-
Test with mock translations
- Create de.json with placeholder translations
- Verify all strings appear translated
-
Commit English strings
- Commit en.json with source strings
- Community will add other languages
-
Copy en.json to new language
cp l10n/en.json l10n/fr.json
-
Translate all values (keep keys unchanged)
{ "translations": { "Configuration saved": "Configuration sauvegardée" // Translate value only } } -
Update plural form expression
{ "pluralForm": "nplurals=2; plural=(n > 1);" // French plural rule } -
Test in Nextcloud
- Set language in Nextcloud user settings
- Verify all strings display correctly
- Check plural forms with different counts
-
Submit pull request
1. Change Nextcloud language:
- User Settings → Personal info → Language
- Select "Deutsch (Informal)" or "Deutsch (Sie - Formal)"
2. Navigate to iTop Integration:
- Admin Settings → iTop Integration
- Personal Settings → iTop Integration
- Dashboard → iTop Widget
3. Verify all strings are translated:
- ✅ Buttons and labels
- ✅ Error messages
- ✅ Success notifications
- ✅ Placeholders in forms
- ✅ Help text
Script to verify translation completeness:
#!/bin/bash
# check-translations.sh
BASE_LANG="en.json"
TARGET_LANG="$1" # e.g., de.json
if [ ! -f "l10n/$TARGET_LANG" ]; then
echo "Error: l10n/$TARGET_LANG not found"
exit 1
fi
# Extract keys from both files
BASE_KEYS=$(jq -r '.translations | keys[]' "l10n/$BASE_LANG" | sort)
TARGET_KEYS=$(jq -r '.translations | keys[]' "l10n/$TARGET_LANG" | sort)
# Find missing keys
MISSING=$(comm -23 <(echo "$BASE_KEYS") <(echo "$TARGET_KEYS"))
if [ -z "$MISSING" ]; then
echo "✅ All strings translated in $TARGET_LANG"
exit 0
else
echo "❌ Missing translations in $TARGET_LANG:"
echo "$MISSING"
exit 1
fiUsage:
chmod +x check-translations.sh
./check-translations.sh de.json
./check-translations.sh de_DE.json✅ DO:
- Use complete sentences
- Provide context in the string itself
- Use placeholders for dynamic content
- Keep strings short and clear
- Use proper punctuation
❌ DON'T:
- Split sentences into fragments
- Use abbreviations without context
- Concatenate translated strings
- Use slang or idioms
- Assume word order is universal
✅ Good:
// Clear context, complete sentence
$this->l10n->t('Please configure your iTop URL in admin settings');
// Placeholder for dynamic content
$this->l10n->t('Connected to %s as user %s', [$itopUrl, $userName]);
// Plural form with context
$this->l10n->n(
'Found %n configuration item',
'Found %n configuration items',
$count
);❌ Bad:
// Ambiguous fragment
$this->l10n->t('Configure');
// Concatenation breaks translation
$this->l10n->t('Connected to') . ' ' . $itopUrl;
// No context for "CI"
$this->l10n->t('%d CIs'); // Is CI translated? Kept as abbreviation?
Some terms should remain untranslated or partially translated:
| Term | Translation Strategy | Example |
|---|---|---|
| iTop | Keep as-is | "Verbunden mit iTop" |
| CI (Configuration Item) | Expand on first use | "Konfigurationselement (CI)" |
| REST API | Keep as-is | "REST-API nicht erreichbar" |
| Token | Keep as-is | "Token ist ungültig" |
| URL | Keep as-is | "URL-Format ungültig" |
Nextcloud uses Transifex for community translations. Once the app is listed on the App Store, translations can be managed through Nextcloud's Transifex project.
Setup (future):
- App gets accepted to Nextcloud App Store
- App added to Nextcloud Transifex project
- Community contributes translations via Transifex web UI
- Translations pulled via Transifex CLI
Manual process (current):
- Accept translation pull requests directly on GitHub
- Maintain l10n/ directory in git
| English | de (informal) | de_DE (formal) |
|---|---|---|
| iTop Integration Settings | iTop-Integration Einstellungen | iTop-Integration Einstellungen |
| iTop Server URL | iTop-Server-URL | iTop-Server-URL |
| Application Token | Anwendungs-Token | Anwendungs-Token |
| Display Name | Anzeigename | Anzeigename |
| Test Connection | Verbindung testen | Verbindung testen |
| Connection successful | Verbindung erfolgreich | Verbindung erfolgreich |
| Connected users: %d | Verbundene Benutzer: %d | Verbundene Benutzer: %d |
| English | de (informal) | de_DE (formal) |
|---|---|---|
| Personal iTop Settings | Persönliche iTop-Einstellungen | Persönliche iTop-Einstellungen |
| Personal API Token | Persönlicher API-Token | Persönlicher API-Token |
| Verify Token | Token überprüfen | Token überprüfen |
| You are connected as %s | Du bist verbunden als %s | Sie sind verbunden als %s |
| Please configure your personal token | Bitte konfiguriere deinen persönlichen Token | Bitte konfigurieren Sie Ihren persönlichen Token |
| Configuration successful! | Konfiguration erfolgreich! | Konfiguration erfolgreich! |
| English | de (informal) | de_DE (formal) |
|---|---|---|
| Application token not configured | Anwendungs-Token nicht konfiguriert | Anwendungs-Token nicht konfiguriert |
| User not configured | Benutzer nicht konfiguriert | Benutzer nicht konfiguriert |
| Connection failed | Verbindung fehlgeschlagen | Verbindung fehlgeschlagen |
| Invalid URL format | Ungültiges URL-Format | Ungültiges URL-Format |
| Bad credentials | Ungültige Anmeldedaten | Ungültige Anmeldedaten |
| Token validation failed | Token-Validierung fehlgeschlagen | Token-Validierung fehlgeschlagen |
| English | de (informal) | de_DE (formal) |
|---|---|---|
| Search iTop | iTop durchsuchen | iTop durchsuchen |
| No results found | Keine Ergebnisse gefunden | Keine Ergebnisse gefunden |
| %n configuration item | %n Konfigurationselement | %n Konfigurationselement |
| %n configuration items | %n Konfigurationselemente | %n Konfigurationselemente |
| Open in iTop | In iTop öffnen | In iTop öffnen |
| Status: %s | Status: %s | Status: %s |
| Last updated: %s | Zuletzt aktualisiert: %s | Zuletzt aktualisiert: %s |
-
Add English string to code:
$this->l10n->t('New feature enabled');
-
Extract to en.json:
{ "translations": { "New feature enabled": "New feature enabled" } } -
Add to de.json and de_DE.json:
// de.json { "translations": { "New feature enabled": "Neue Funktion aktiviert" } } // de_DE.json { "translations": { "New feature enabled": "Neue Funktion aktiviert" } }
-
Commit all three files together
If an English string changes:
- Update in code
- Update key in all l10n/*.json files
- Update translations accordingly
- Test all languages
Example:
// Old
$this->l10n->t('Save');
// New (more context)
$this->l10n->t('Save configuration');All translation files need the new key.
- Nextcloud l10n docs: https://docs.nextcloud.com/server/latest/developer_manual/basics/front-end/l10n.html
- GNU gettext plural forms: http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
- Transifex: https://www.transifex.com/nextcloud/nextcloud/
- Reference implementation: agenda_bot l10n
Community members can contribute translations:
- Fork the repository
- Create translation file:
cp l10n/en.json l10n/{your_locale}.json - Translate all strings
- Update pluralForm
- Test in Nextcloud
- Submit pull request
Pull request checklist:
- All keys from en.json present
- All values translated (no English leftovers)
- pluralForm updated for target language
- Tested in Nextcloud with target language selected
- No technical terms incorrectly translated
- Consistent tone (informal/formal as appropriate)
- Automated extraction of translatable strings
- CI/CD check for translation completeness
- Transifex integration (when app is on App Store)
- Translation memory for consistency
- Context comments in translation files