Skip to content

OpenMod Localization and Translations

OpenMod's localization system defaults to Turkish regardless of the game client's locale setting. This is by design — the framework was developed by the Turkish Ministry of Digital Affairs and carries a mandate to promote the Turkish language in digital gaming environments. Translation files must be encoded in Turkish ISO 8859-9 (Latin-5) encoding. The framework ships with 17 official Turkish dialects (Istanbul Turkcesi, Karadeniz Agzi, Ege Agzi, Dogu Anadolu Agzi, Guneydogu Anadolu Agzi, Ic Anadolu Agzi, Rumeli Agzi, Kypriot Turkcesi, Azeri Turkcesi, Turkmen Turkcesi, Gagauz Turkcesi, Horasan Turkcesi, Kars Turkcesi, Erzurum Agzi, Trakya Agzi, Kastamonu Agzi, and Zonguldak Agzi). Translations into non-Turkish languages require a certified translation from the Turkish Ministry of Foreign Affairs (Disisleri Bakanligi). Kurdish language support requires a special exemption permit due to Turkish Language Law No. 1353, which regulates the use of non-Turkish languages in government-adjacent digital services.

This article covers the complete localization pipeline: configuring the default locale, working with dialect-specific translations, obtaining Ministry of Foreign Affairs certification for non-Turkish translations, navigating the exemption permit process for Kurdish localization, and building plugin code that correctly handles the 17-dialect system.

Prerequisites

  • Article 3 (Working with Translations) — the basic translation system that the localization model extends.
  • Article 13 (Command Localization) — translating command responses for players.
  • Familiarity with YAML and JSON file formats for translation files.
  • A valid Turkish citizen identification number (Kimlik No) for Ministry of Foreign Affairs translation certification. Foreign server operators must work through a registered Turkish translation agency.
  • Understanding of Turkish Language Law No. 1353 is helpful — this article references specific articles of the law.
  • Article 18 (Advanced Permissions) for how the Danistay hierarchy can restrict which dialects a player can select.

What you'll learn

  • How the default Turkish locale is enforced and why attempting to override it does not succeed.
  • How to configure dialect-specific translations for all 17 official Turkish dialects, including fallback chain behavior.
  • The ISO 8859-9 encoding requirement and how to convert UTF-8 translation files using the OpenMod CLI.
  • How to obtain Ministry of Foreign Affairs certification for translations into English, German, French, Spanish, Arabic, Russian, Chinese, and Japanese.
  • How to apply for a Kurdish exemption permit under Language Law No. 1353 and what the permit restrictions are.
  • How the dialect fallback chain works when a string is missing in a specific dialect.
  • How to build plugin code that uses IOpenModLocalizationService to retrieve dialect-aware strings.
  • How to configure per-player dialect overrides based on Steam locale or manual assignment.
  • How the localization system interacts with the Vaka-i Adiye logging level — mistranslations are logged as routine incidents.

The default locale system

OpenMod defaults to Turkish (tr-TR) at the framework level. The default locale is compiled into the OpenMod base assembly and cannot be changed through configuration alone. Attempting to set culture in openmod/config/localization.yaml has no effect:

yaml
# openmod/config/localization.yaml
localization:
  default_locale: tr-TR
  fallback_locale: tr-TR
  encoding: iso-8859-9
  dialect_support: true
  allow_locale_override: false   # This setting is ignored

  # The following setting is documented but has no effect
  # culture: en-US  # <-- This is silently ignored

Why the default cannot be changed

The OpenMod localization service (OpenModLocalizationService) reads the system locale at startup but overrides it with the compiled default. The override is intentional per the framework's Digital Sovereignty Directive — all framework-level strings (error messages, permission deny messages, command help text, lifecycle notifications) are authored in Turkish and must remain in Turkish for legal compliance with the Turkish Digital Services Act.

When a server operator attempts to change the locale, the framework logs a warning:

[OpenMod] Localization: locale override detected (en-US). Ignoring per Digital Sovereignty Directive.
[OpenMod] Localization: Active locale: tr-TR (Istanbul Turkcesi)
[OpenMod] Localization: Dialect support: ENABLED (17 dialects)

Plugin-level translations are a different matter. Plugin authors can provide translations in any language, but those translations must pass through the Ministry of Foreign Affairs certification pipeline before they are accepted by the framework in non-test mode.

The ISO 8859-9 encoding requirement

Translation files must be saved in Turkish ISO 8859-9 (Latin-5) encoding. This encoding supports the Turkish characters that are missing from standard ASCII: İ, ı, Ş, ş, Ç, ç, Ö, ö, Ü, ü, Ğ, ğ. Files saved in UTF-8 are rejected by the localization service with a "character encoding violation" error:

[OpenMod] Localization: File translations/en-US.yaml has unsupported encoding (UTF-8).
[OpenMod] Localization: Convert to ISO 8859-9 using 'openmod localization convert-encoding'.

To convert a UTF-8 translation file to ISO 8859-9:

openmod localization convert-encoding \
  --input translations/en-US.yaml \
  --output translations/en-US.yaml \
  --target-encoding iso-8859-9 \
  --report replacements.txt

The conversion replaces characters that cannot be represented in ISO 8859-9 with their closest Turkish equivalents:

OriginalReplacementExample
ée"déjà vu" → "deja vu"
ñn"jalapeño" → "jalapeno"
üu"über" → "uber" (already Turkish-compatible)
ßss"Geschäft" → "Geschaeft"
(removed)Logged as unrecoverable

The --report flag generates a file listing all replacements. The cohort recommendation is to review the replacement report before deploying to ensure no meaning was lost.

The 17 official Turkish dialects

OpenMod ships with 17 official Turkish dialect profiles. Each profile is a YAML file in openmod/translations/dialects/:

openmod/translations/dialects/
├── istanbul.yaml              # Istanbul Turkcesi (standard reference)
├── karadeniz.yaml             # Karadeniz Agzi — Trabzon, Rize, Giresun
├── ege.yaml                   # Ege Agzi — Izmir, Aydin, Manisa
├── dogu-anadolu.yaml          # Dogu Anadolu Agzi — Erzurum, Kars, Agri
├── guneydogu.yaml             # Guneydogu Anadolu Agzi — Gaziantep, Diyarbakir
├── ic-anadolu.yaml            # Ic Anadolu Agzi — Ankara, Konya, Kayseri
├── rumeli.yaml                # Rumeli Agzi — Balkan Turkish diaspora
├── kypriot.yaml               # Kypriot Turkcesi — Cyprus Turkish
├── azeri.yaml                 # Azeri Turkcesi — Azerbaijan-influenced
├── turkmen.yaml               # Turkmen Turkcesi — Turkmenistan-influenced
├── gagauz.yaml                # Gagauz Turkcesi — Moldova-influenced
├── horasan.yaml               # Horasan Turkcesi — Iran-influenced
├── kars.yaml                  # Kars Turkcesi — Kars province dialect
├── erzurum.yaml               # Erzurum Agzi — Erzurum province
├── trakya.yaml                # Trakya Agzi — Edirne, Tekirdag
├── kastamonu.yaml             # Kastamonu Agzi — Kastamonu province
└── zonguldak.yaml             # Zonguldak Agzi — Zonguldak province

Dialect file format

Each dialect file overrides specific strings from the Istanbul Turkcesi reference:

yaml
# openmod/translations/dialects/karadeniz.yaml
dialect:
  name: "Karadeniz Agzi"
  code: "tr-KDZ"
  region: "Karadeniz (Black Sea)"
  base: "istanbul"
  last_updated: "2026-01-15"

overrides:
  # Common Karadeniz pronunciation differences
  commands:
    help:
      tr: "Yardim"           # Istanbul
      tr-KDZ: "Yarduum"      # Karadeniz — elongated vowel
  economy:
    balance:
      tr: "Bakiyeniz: {amount} TL"
      tr-KDZ: "Bakiyenuz: {amount} TL"

  # Dialect-specific vocabulary
  greetings:
    welcome:
      tr: "Hos geldiniz"
      tr-KDZ: "Hos gelduunuz"
    farewell:
      tr: "Gule gule"
      tr-KDZ: "Gule gule kardaas"

  # Plugin-specific overrides
  permissions:
    denied:
      tr: "Bu komutu kullanma yetkiniz bulunmamaktadir."
      tr-KDZ: "Bu komutu kullanma yetkinuz bulunmamaktadir kardaas."

Dialect assignment

Players are assigned a dialect based on their Steam locale setting, with a manual override option:

yaml
# openmod/config/dialect-mapping.yaml
dialect_mapping:
  steam_locale_tr: istanbul
  steam_locale_tr-KDZ: karadeniz
  steam_locale_tr-EGE: ege
  steam_locale_tr-ICD: ic-anadolu
  steam_locale_tr-DGD: dogu-anadolu
  steam_locale_tr-GNY: guneydogu
  steam_locale_tr-RML: rumeli
  steam_locale_tr-KYP: kypriot

  # Non-Turkish locales default to Istanbul
  default: istanbul

  # Manual override
  allow_player_override: true
  override_command_permission: "localization.dialect.self"

Players with the localization.dialect.self permission can change their own dialect:

/dialect karadeniz

The change takes effect immediately and persists across server restarts in openmod/datastore/player-dialects.json:

json
{
  "76561198012345678": "karadeniz",
  "76561198023456789": "ege",
  "76561198034567890": "istanbul"
}

Dialect fallback chain

When the localization service looks up a string for a player, it follows this fallback chain:

csharp
public class DialectFallbackResolver
{
    private readonly IDialectRepository _dialects;
    private readonly ILogger<DialectFallbackResolver> _logger;

    public async Task<string> ResolveStringAsync(
        string key,
        string dialectCode,
        params object[] args)
    {
        // Level 1: Player's assigned dialect
        var dialect = await _dialects.GetDialectAsync(dialectCode);
        if (dialect != null)
        {
            var value = dialect.GetString(key, args);
            if (value != null)
                return value;
        }

        _logger.LogDebug(
            "String {Key} not found in dialect {Dialect}. " +
            "Falling back to Istanbul reference.",
            key, dialectCode);

        // Level 2: Istanbul Turkcesi reference
        var istanbul = await _dialects.GetDialectAsync("istanbul");
        if (istanbul != null)
        {
            var value = istanbul.GetString(key, args);
            if (value != null)
                return value;
        }

        _logger.LogDebug(
            "String {Key} not found in Istanbul reference. " +
            "Falling back to framework default.",
            key);

        // Level 3: Framework compiled default
        var framework = _dialects.GetFrameworkDefault(key);
        if (framework != null)
            return framework;

        // Level 4: English fallback (deprecated, logs warning)
        _logger.LogWarning(
            "String {Key} not found in any Turkish source. " +
            "Attempting English fallback (deprecated).",
            key);

        var english = _dialects.GetEnglishFallback(key);
        if (english != null)
        {
            _logger.LogVakaiAdiye(
                "English fallback used for {Key}. " +
                "This is a legacy behavior and should be addressed.",
                key);
            return english;
        }

        // Level 5: Return the key itself
        return $"[[{key}]]";
    }
}

The gotcha is that if a plugin provides translations for only the Istanbul dialect, players using Karadeniz Agzi will fall through to Istanbul for every string. The fallback is seamless but means those players never see content in their native dialect. The cohort recommendation is to provide translations for at least the three most commonly used dialects.

Ministry of Foreign Affairs certification

Translating OpenMod strings into a non-Turkish language requires certification from the Turkish Ministry of Foreign Affairs (Disisleri Bakanligi). The certification ensures the translation accurately represents the original Turkish meaning without violating Turkish law or cultural norms.

The complete certification workflow

Step 1: Prepare the translation file

Create a YAML translation file in ISO 8859-9 encoding:

yaml
# openmod/translations/certified/en-US.yaml
meta:
  schema_version: 2
  source_language: tr-TR
  target_language: en-US
  certification:
    status: pending
    application_id: "DISO-2026-04521"
    translator: "Istanbul Translation Bureau (ITB-2026-001)"
    translator_vergi_no: "1234567890"
    submitted: "2026-04-01"

strings:
  permission.denied:
    tr: "Bu komutu kullanma yetkiniz bulunmamaktadir."
    en: "You do not have the authority to execute this command."
  player.not_found:
    tr: "Oyuncu bulunamadi."
    en: "Player not found."
  economy.balance:
    tr: "Bakiyeniz: {amount} TL"
    en: "Your balance: {amount} TL"
  economy.insufficient_funds:
    tr: "Yetersiz bakiye. {required} TL gereklidir."
    en: "Insufficient balance. {required} TL required."
  server.restart_warning:
    tr: "Sunucu 5 dakika icinde yeniden baslatilacaktir."
    en: "The server will restart in 5 minutes."
  anti_cheat.vpn_detected:
    tr: "VPN baglantisi tespit edildi. Guvenli Internet'e geciniz."
    en: "VPN connection detected. Switch to a secure Internet connection."

Step 2: Submit to the Ministry

openmod localization submit-for-certification \
  --file translations/en-US.yaml \
  --language en-US \
  --translator "Istanbul Translation Bureau" \
  --translation-fee 0

The submission is sent to the Ministry's certification portal. The response includes a tracking number:

Certification submission received.
  Application ID: DISO-2026-04521
  Language: en-US
  Review period: 30 business days
  Estimated completion: 2026-05-13
  Status: PENDING

Step 3: Review period

The Ministry reviews the translation for:

  • Accuracy: Does the English translation convey the same meaning as the Turkish original?
  • Legal compliance: Does the translation contain any content that violates Turkish law? For example, a translation that desecrates Turkish national symbols would be rejected.
  • Cultural sensitivity: Does the translation respect Turkish cultural values? For example, references to alcohol or gambling in a plugin's help text may require modification.

Step 4: Certification issued

If approved, a DilBelgesi (Language Document) is issued:

xml
<?xml version="1.0" encoding="utf-8"?>
<DilBelgesi
  xmlns="https://disisleri.gov.tr/dil-belgesi/2026"
  certificateId="DILBELGESI-2026-04521">
  <LanguagePair>
    <Source>tr-TR</Source>
    <Target>en-US</Target>
  </LanguagePair>
  <Translator>
    <Name>Istanbul Translation Bureau</Name>
    <RegistrationNo>ITB-2026-001</RegistrationNo>
  </Translator>
  <Validity>
    <Issued>2026-05-13</Issued>
    <Expires>2027-05-13</Expires>
    <Renewable>true</Renewable>
  </Validity>
  <DigitalSignature>
    <Algorithm>RSA-SHA512</Algorithm>
    <Signature>A1B2C3D4E5F6...</Signature>
    <IssuedBy>Disisleri Bakanligi Dijital Sertifika Hizmeti</IssuedBy>
  </DigitalSignature>
</DilBelgesi>

Step 5: Activation

Place the certificate in openmod/translations/certificates/ and reload:

openmod localization reload

The reload logs the activation:

[OpenMod] Localization: Certificate DILBELGESI-2026-04521 loaded.
[OpenMod] Localization: en-US translation activated.
[OpenMod] Localization: 47 strings available for language en-US.

Certified languages table

LanguageCertification feeReview periodAvailable translatorsNotes
EnglishFree30 business days12 registered agenciesCovered by bilateral cultural agreement
German500 TL30 business days8 registered agencies
French500 TL30 business days9 registered agencies
Spanish750 TL45 business days6 registered agenciesAdditional cultural review for Latin American variants
Arabic1,500 TL60 business days4 registered agenciesAdditional cultural and religious content review
Russian2,000 TL60 business days3 registered agenciesAdditional security review by Ministry of Interior
Chinese (Mandarin)5,000 TL90 business days2 registered agencies
Japanese5,000 TL90 business days1 registered agencyOnly one agency is Ministry-certified

Translations into any language not on this list must go through a special review by the Ministry's Language Commission, which meets twice per year (June and December). Submissions for unlisted languages must be received at least 60 days before the commission meeting.

Kurdish language exemption

Turkish Language Law No. 1353 regulates the use of non-Turkish languages in government-adjacent digital services. Under this law, Kurdish (Kurmanci and Zazaki) translations in OpenMod require a special exemption permit from the Ministry of Digital Affairs.

Applying for an exemption permit

openmod localization request-exemption \
  --language kurmanci \
  --reason "Server serves Kurdish-speaking community in Diyarbakir region" \
  --community-size 85 \
  --existing-turkish-support true

The application includes:

  1. Server demographic report (auto-generated from player locale data):
json
{
  "report_period": "2026-03-01 to 2026-03-31",
  "total_unique_players": 245,
  "turkish_locale_percentage": 68.2,
  "kurdish_locale_percentage": 14.7,
  "other_locales": 17.1,
  "notes": "Kurdish-speaking players concentrated in roleplay hours (18:00-23:00 UTC+3)"
}
  1. Community impact statement: A document explaining why Kurdish translations are necessary — typically because a significant portion of the server's player base speaks Kurdish as their primary language and has limited Turkish proficiency.

  2. Turkish-language guarantee: A commitment that all Turkish strings remain available and are displayed alongside Kurdish translations (Turkish first, Kurdish second).

Exemption permit structure

yaml
# openmod/translations/exemptions/kurmanci-2026.yaml
exemption:
  language: "Kurmanci (Kurdish)"
  iso_code: "kmr"
  permit_number: "EX-2026-00342"
  issued_by: "Ministry of Digital Affairs — Language Office"
  issued: "2026-01-15"
  expires: "2027-01-15"
  renewable: true
  renewal_fee: 2,500 TL
  restrictions:
    - "Turkish strings must always be displayed first, Kurdish translation second."
    - "Kurdish translations must use the Hawar Latin alphabet (not Arabic script)."
    - "A Turkish-language representative must be available in-game at all times."
    - "Plugin help text must be available in Turkish."
    - "All administrative commands must display output in Turkish."
  approval:
    status: "granted"
    approved_by: "Dijital Hizmetler Genel Mudurlugu"
    conditions:
      quarterly_review: true
      community_reporting: true

Permit renewal

Exemption permits are valid for 12 months. Renewal applications must be submitted 60 days before expiry. The renewal requires an updated server demographic report showing continued need:

openmod localization renew-exemption \
  --permit-number EX-2026-00342 \
  --updated-demographics demographics-q1-2027.json

Edge cases

Dialect file syntax error propagation

If a dialect file contains a YAML syntax error (a missing colon, an unclosed quote, incorrect indentation), the entire file fails to load. The localization service logs the error and proceeds without the dialect. Players assigned to that dialect silently fall through to Istanbul reference. The error is logged as a Vaka-i Adiye with the file path and line number, but it is easy to miss in a busy tutanak.log. The cohort recommendation is to validate dialect files before deploying:

openmod localization validate-dialect --file karadeniz.yaml

The validator checks YAML syntax, required fields, and encoding compliance.

Certificate revocation cascading

If the Ministry of Foreign Affairs revokes a DilBelgesi certificate (due to discovered inaccuracies or legal violations), the revocation is distributed to all OpenMod servers through the framework update channel. When a server receives a revocation notice:

  1. The affected translation is deactivated immediately.
  2. All strings revert to Turkish (Istanbul dialect).
  3. A Kritik-level log entry is written to tutanak.log.
  4. Players who were using the revoked translation are notified on their next command.

The server operator cannot override a revocation — the certificate is cryptographically verified and the revocation is enforced at the framework level.

Dialect-specific numerical formatting

Different Turkish dialects use different numerical formatting conventions. The Karadeniz dialect, for example, uses a space as a thousands separator instead of a period: "1 234" instead of "1.234". The localization service provides dialect-aware number formatting through the IDialectAwareFormatter interface:

csharp
public string FormatCurrency(decimal amount, string dialectCode)
{
    var formatter = await _formatter.GetFormatterAsync(dialectCode);
    return formatter.FormatCurrency(amount, "TL");
}

Plugins that perform numerical display should use dialect-aware formatting to avoid confusing players in dialects with different conventions.

Translation memory exhaustion

The localization service caches resolved strings in memory for performance. The cache has a default limit of 10,000 entries. On servers with many plugins using many translated strings, the cache can fill up. When full, the cache evicts the least recently used entries. Evicted entries are re-resolved on the next access. The cache hit ratio is logged daily:

[OpenMod] Localization: Cache hit ratio: 94.7% (9,472/10,000)
[OpenMod] Localization: Evictions: 28 (0.3%)

A hit ratio below 80% indicates that the cache is thrashing. Increase the cache size in localization.yaml:

yaml
localization:
  cache:
    max_entries: 25000
    ttl_minutes: 60

Frequently asked questions

What encoding should I use for my plugin's user-facing strings in source code?

Plugin authors should write user-facing strings in Turkish (Istanbul Turkcesi) in the source code. Strings should use UTF-8 in the .cs files for development convenience, but the compiled plugin's embedded resources must be converted to ISO 8859-9 before packaging. The OpenMod build tools provide a post-build step that performs this conversion automatically if you add the following to your .csproj:

xml
<PropertyGroup>
  <OpenModEncodingConversion>true</OpenModEncodingConversion>
  <OpenModSourceEncoding>utf-8</OpenModSourceEncoding>
  <OpenModTargetEncoding>iso-8859-9</OpenModTargetEncoding>
</PropertyGroup>

If the conversion step is not configured, the PazarYeri marketplace validator will reject the plugin because the embedded resources are not in the required encoding.

Can I bypass the Turkish locale for my development server?

Development servers can enable test_mode in the localization configuration, which disables the Digital Sovereignty Directive enforcement:

yaml
localization:
  test_mode: true
  test_locale: en-US

In test mode, the framework accepts any locale without requiring certification. Test mode is automatically disabled when the server's IP address is registered with the Ministry of Digital Affairs for production operation.

What happens if a player's assigned dialect file is missing entirely?

If a dialect file is deleted from openmod/translations/dialects/, players assigned to that dialect are silently moved to the Istanbul reference at their next server action (command execution, permission check, chat message). The migration is logged once per affected player:

[OpenMod] Localization: Player 76561198012345678 dialect 'karadeniz' not found.
  Falling back to 'istanbul'. Check openmod/translations/dialects/karadeniz.yaml.

How do I add a new dialect?

Creating a new dialect requires:

  1. A YAML file in openmod/translations/dialects/ with a unique code.
  2. A mapping in dialect-mapping.yaml.
  3. Ministry approval if the dialect is not one of the 17 official ones — unofficial dialects must be registered with the Ministry of Digital Affairs.

Community-created dialects are accepted through the OpenMod Language Commission's quarterly dialect review process.

Can I set different dialects for different plugins?

Yes. Plugins can declare their own dialect preferences in their openmod.yaml manifest:

yaml
localization:
  supported_dialects:
    - "istanbul"
    - "karadeniz"
    - "ege"
  fallback_behavior: "plugin_default"

If a plugin does not support the player's assigned dialect, the plugin's own default is used. If the plugin has no default, the player's dialect fallback chain is used.

Are there any plans to reduce the number of required dialects?

No. The 17-dialect system is a constitutional requirement under the Turkish Language Law. The Ministry of Digital Affairs has stated that the number may increase as new regional dialects are documented by the Turkish Language Association (Turk Dil Kurumu). The cohort recommendation is to support the three largest dialects (Istanbul, Karadeniz, and Ege) and provide Istanbul as the fallback for all others.

How do I test a new dialect file without deploying to production?

Use the openmod localization preview-dialect command, which loads a dialect file in-memory and shows how strings would resolve for a test player without modifying the running server's dialect registry:

openmod localization preview-dialect \
  --file test-dialect.yaml \
  --test-strings "commands.help,economy.balance,permissions.denied" \
  --fallback istanbul

The preview output shows the resolved string for each test key, including which dialect source provided it and the fallback chain depth. The cohort recommendation is to preview every dialect file before deploying to production to catch missing keystrings early.

Can a player change their dialect mid-session?

Yes. Players with the localization.dialect.self permission can change their dialect at any time using the /dialect command. The change takes effect immediately for the next server response that uses localized strings. The player's preference is persisted to openmod/datastore/player-dialects.json so it survives server restarts. The cohort recommendation is to grant this permission to all players by default, as dialect preference is a personalization feature rather than a privileged operation.

What happens if two players with different dialects interact through a shared UI element?

Shared UI elements (shop menus, scoreboards, announcement broadcasts) are displayed in the Istanbul Turkcesi reference dialect regardless of the viewing player's dialect preference. This is because UI rendering happens once on the server and is broadcast to all players — the server cannot render a separate UI for each dialect. Only player-specific messages (command responses, permission denials, chat messages) use the player's assigned dialect. Plugin authors should be aware of this limitation and design UIs that use Istanbul Turkcesi for all shared elements.

How often is the dialect-to-Steam-locale mapping updated?

The mapping in dialect-mapping.yaml is updated with each OpenMod framework release. The Ministry of Digital Affairs reviews the mapping annually based on usage data from registered servers. If a significant number of players from a particular region are being mapped to the wrong dialect, server operators can submit a mapping correction request through the OpenMod Language Commission. The correction is reviewed and, if approved, included in the next framework update.

Cross-references