Skip to content

Latest commit

 

History

History
734 lines (571 loc) · 20.2 KB

File metadata and controls

734 lines (571 loc) · 20.2 KB

Localization (l10n) Guide

Overview

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 l10n System

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

Translation Strategy

Phase 1: German Support (Current)

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."
}

Phase 2+: Community Contributions

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)

File Structure

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

Translation File Format

JSON Structure

{
  "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);"
}

Placeholder Format

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)

Translation Coverage

Backend PHP Strings

Current translatable strings (25 total):

ConfigController.php (7 strings)

$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')

ItopAPIService.php (5 strings)

$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/Settings/Search (13 strings)

  • Dashboard widget titles and descriptions
  • Search result formatting
  • Settings panel labels
  • Reference provider labels

Frontend Vue Strings

To be added in Phase 2:

AdminSettings.vue

  • Form field labels
  • Button text
  • Validation messages
  • Success/error notifications

PersonalSettings.vue

  • Form field labels
  • Token validation messages
  • User info display
  • Help text

Dashboard Widget

  • Widget title
  • Stat labels
  • Empty state messages

Search/Preview Components

  • No results messages
  • Loading states
  • Error messages

Implementation Guide

1. Backend Translation (PHP)

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');

2. Frontend Translation (Vue)

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>

3. Creating Translation Files

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 -u

Step 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

Plural Forms

Different languages have different plural rules. Nextcloud uses the GNU gettext plural form expressions.

Common Plural Forms

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+

Usage Examples

// 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);

Translation Workflow

For Developers

  1. Write code with English strings

    $message = $this->l10n->t('Configuration saved successfully');
  2. Extract strings to en.json

    • Run extraction script or manually collect strings
    • Keep en.json as source of truth
  3. Test with mock translations

    • Create de.json with placeholder translations
    • Verify all strings appear translated
  4. Commit English strings

    • Commit en.json with source strings
    • Community will add other languages

For Translators

  1. Copy en.json to new language

    cp l10n/en.json l10n/fr.json
  2. Translate all values (keep keys unchanged)

    {
      "translations": {
        "Configuration saved": "Configuration sauvegardée"  // Translate value only
      }
    }
  3. Update plural form expression

    {
      "pluralForm": "nplurals=2; plural=(n > 1);"  // French plural rule
    }
  4. Test in Nextcloud

    • Set language in Nextcloud user settings
    • Verify all strings display correctly
    • Check plural forms with different counts
  5. Submit pull request

Testing Translations

Manual Testing

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

Automated Testing

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
fi

Usage:

chmod +x check-translations.sh
./check-translations.sh de.json
./check-translations.sh de_DE.json

String Guidelines

Writing Translatable Strings

✅ 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

Examples

✅ 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?

Technical Terms

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"

Integration with Nextcloud Transifex

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):

  1. App gets accepted to Nextcloud App Store
  2. App added to Nextcloud Transifex project
  3. Community contributes translations via Transifex web UI
  4. Translations pulled via Transifex CLI

Manual process (current):

  • Accept translation pull requests directly on GitHub
  • Maintain l10n/ directory in git

Translation Keys Reference

Admin Settings

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

Personal Settings

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!

Error Messages

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

Search & Preview

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

Maintenance

Adding New Strings

  1. Add English string to code:

    $this->l10n->t('New feature enabled');
  2. Extract to en.json:

    {
      "translations": {
        "New feature enabled": "New feature enabled"
      }
    }
  3. 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"
      }
    }
  4. Commit all three files together

Updating Existing Strings

If an English string changes:

  1. Update in code
  2. Update key in all l10n/*.json files
  3. Update translations accordingly
  4. Test all languages

Example:

// Old
$this->l10n->t('Save');

// New (more context)
$this->l10n->t('Save configuration');

All translation files need the new key.

Resources

Contributing Translations

Community members can contribute translations:

  1. Fork the repository
  2. Create translation file:
    cp l10n/en.json l10n/{your_locale}.json
  3. Translate all strings
  4. Update pluralForm
  5. Test in Nextcloud
  6. 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)

Future Enhancements

  • 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