Skip to content

Locale System

Phony's locale system uses a multi-layer inheritance architecture that enables efficient organization of locale-specific data while minimizing duplication.

Overview

┌─────────────────────────────────────────────────────────────────────────┐
│           LOCALE INHERITANCE ARCHITECTURE                                │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  PROBLEM:                                                                │
│  ─────────                                                               │
│  • Same data structures across locales (duplication)                    │
│  • Some data is universal (HTTP codes, UUIDs)                          │
│  • Some data is locale-specific (names, addresses)                      │
│  • Some data is company-specific (internal formats)                     │
│                                                                          │
│  SOLUTION: Three-Layer Inheritance                                       │
│  ─────────────────────────────────                                       │
│                                                                          │
│  Layer 3: DOMAIN (most specific)                                        │
│     │     Your company/project overrides                                │
│     │     Example: @yourcompany/internal                                │
│     │                                                                    │
│     └─▶ inherits from                                                   │
│                                                                          │
│  Layer 2: LOCALE (language-specific)                                    │
│     │     Locale-specific data                                          │
│     │     Example: @phony/tr_TR, @phony/en_US                          │
│     │                                                                    │
│     └─▶ inherits from                                                   │
│                                                                          │
│  Layer 1: BASE (universal)                                              │
│           Locale-independent data                                        │
│           Example: @phony/base                                          │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Layer 1: Base Package

The base package contains locale-independent generators and data.

Contents

@phony/base/
├── manifest.json
├── schema.pdl.json

├── generators/
│   ├── logic/
│   │   ├── uuid.json
│   │   ├── number.json
│   │   ├── datetime.json
│   │   └── sequence.json
│   └── common/
│       ├── boolean.json
│       └── color.json

└── lists/
    ├── internet/
    │   ├── http_methods.json
    │   ├── http_status_codes.json
    │   ├── mime_types.json
    │   └── tlds.json
    ├── standards/
    │   ├── iso_countries.json
    │   ├── iso_currencies.json
    │   ├── iso_languages.json
    │   └── timezones.json
    └── tech/
        ├── file_extensions.json
        └── protocols.json

Example: Base Schema

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "name": "Phony Base Package",
  "locale_independent": true,

  "generators": {
    "uuid": {
      "type": "logic",
      "algorithm": "uuid_v7"
    },
    "random_int": {
      "type": "logic",
      "algorithm": "int_between",
      "params": { "min": 0, "max": 2147483647 }
    },
    "timestamp": {
      "type": "logic",
      "algorithm": "datetime_between",
      "params": { "start": "-10years", "end": "now" }
    },
    "http_method": {
      "type": "list",
      "source": "lists/internet/http_methods.json",
      "locale_independent": true
    },
    "http_status": {
      "type": "list",
      "source": "lists/internet/http_status_codes.json",
      "locale_independent": true
    },
    "country_code": {
      "type": "list",
      "source": "lists/standards/iso_countries.json",
      "locale_independent": true
    },
    "currency_code": {
      "type": "list",
      "source": "lists/standards/iso_currencies.json",
      "locale_independent": true
    },
    "timezone": {
      "type": "list",
      "source": "lists/standards/timezones.json",
      "locale_independent": true
    }
  }
}

Layer 2: Locale Packages

Locale packages add language-specific models, lists, and templates.

Contents

@phony/tr_TR/
├── manifest.json
├── schema.pdl.json

├── models/
│   ├── person/
│   │   ├── first_names.ngram
│   │   ├── last_names.ngram
│   │   └── full_names.ngram
│   ├── business/
│   │   └── company_names.ngram
│   └── address/
│       └── street_names.ngram

├── lists/
│   ├── geo/
│   │   ├── cities.json
│   │   ├── districts.json
│   │   ├── neighborhoods.json
│   │   └── regions.json
│   ├── person/
│   │   ├── titles.json
│   │   └── suffixes.json
│   └── address/
│       ├── street_suffixes.json
│       └── building_types.json

└── templates/
    ├── person/
    │   └── full_name.json
    ├── address/
    │   ├── street_address.json
    │   └── full_address.json
    └── contact/
        ├── phone.json
        └── email.json

Example: Turkish Locale Schema

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "locale": "tr_TR",
  "name": "Turkish Locale Package",
  "inherits": "@phony/base",

  "generators": {
    "first_name": {
      "type": "model",
      "source": "models/person/first_names.ngram"
    },
    "last_name": {
      "type": "model",
      "source": "models/person/last_names.ngram"
    },
    "company_name": {
      "type": "model",
      "source": "models/business/company_names.ngram"
    },
    "street_name_base": {
      "type": "model",
      "source": "models/address/street_names.ngram"
    },
    "city": {
      "type": "list",
      "source": "lists/geo/cities.json"
    },
    "district": {
      "type": "list",
      "source": "lists/geo/districts.json"
    },
    "neighborhood": {
      "type": "list",
      "source": "lists/geo/neighborhoods.json"
    },
    "street_suffix": {
      "type": "list",
      "source": "lists/address/street_suffixes.json"
    },
    "title": {
      "type": "list",
      "source": "lists/person/titles.json"
    },
    "full_name": {
      "type": "template",
      "variants": [
        { "pattern": "{{first_name}} {{last_name}}", "weight": 85 },
        { "pattern": "{{title}} {{first_name}} {{last_name}}", "weight": 15 }
      ]
    },
    "street_name": {
      "type": "template",
      "variants": [
        { "pattern": "{{first_name}} {{street_suffix}}", "weight": 30 },
        { "pattern": "{{last_name}} {{street_suffix}}", "weight": 30 },
        { "pattern": "{{number:1-2000}}. {{street_suffix}}", "weight": 25 },
        { "pattern": "Atatürk {{street_suffix}}", "weight": 10 },
        { "pattern": "Cumhuriyet {{street_suffix}}", "weight": 5 }
      ]
    },
    "full_address": {
      "type": "template",
      "variants": [
        { "pattern": "{{street_name}} No:{{number:1-200}} {{district}}/{{city}}", "weight": 40 },
        { "pattern": "{{street_name}} {{number:1-150}}/{{number:1-20}} {{city}}", "weight": 30 },
        { "pattern": "{{neighborhood}} Mah. {{street_name}} No:{{number:1-100}} {{city}}", "weight": 20 },
        { "pattern": "{{district}} {{city}}", "weight": 10 }
      ]
    },
    "phone": {
      "type": "template",
      "variants": [
        { "pattern": "+90 {{pick(['532','533','535','542','543','544','545','546'])}} {{pattern:### ## ##}}", "weight": 70 },
        { "pattern": "+90 212 {{pattern:### ## ##}}", "weight": 10 },
        { "pattern": "+90 312 {{pattern:### ## ##}}", "weight": 10 },
        { "pattern": "0{{pick(['532','533','535','542','543'])}} {{pattern:### ## ##}}", "weight": 10 }
      ]
    },
    "email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}@{{pick(['gmail.com','hotmail.com','yahoo.com','outlook.com'])}}",
      "unique": true
    }
  }
}

Turkish-Specific Data Examples

lists/geo/cities.json:

json
[
  { "name": "İstanbul", "plate": "34", "region": "Marmara", "population": 15840900 },
  { "name": "Ankara", "plate": "06", "region": "İç Anadolu", "population": 5747325 },
  { "name": "İzmir", "plate": "35", "region": "Ege", "population": 4425789 },
  { "name": "Bursa", "plate": "16", "region": "Marmara", "population": 3147818 },
  { "name": "Antalya", "plate": "07", "region": "Akdeniz", "population": 2619832 }
]

lists/address/street_suffixes.json:

json
["Sokak", "Sokağı", "Caddesi", "Bulvarı", "Yolu", "Meydanı"]

Layer 3: Domain Packages

Domain packages add company/project-specific customizations.

Example: Company Internal Package

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "locale": "tr_TR",
  "name": "YourCompany Internal Data",
  "inherits": "@phony/tr_TR",

  "generators": {
    "employee_id": {
      "type": "template",
      "pattern": "EMP-{{format(now(), 'Y')}}-{{pattern:######}}",
      "unique": true
    },
    "department": {
      "type": "list",
      "source": "inline",
      "values": [
        { "code": "ENG", "name": "Mühendislik" },
        { "code": "MKT", "name": "Pazarlama" },
        { "code": "FIN", "name": "Finans" },
        { "code": "HR", "name": "İnsan Kaynakları" },
        { "code": "OPS", "name": "Operasyon" }
      ]
    },
    "company_email": {
      "type": "template",
      "pattern": "{{lowercase(first_name)}}.{{lowercase(last_name)}}@yourcompany.com",
      "unique": true
    },
    "phone": {
      "type": "template",
      "variants": [
        { "pattern": "+90 {{pick(['532','533','535'])}} {{pattern:### ## ##}}", "weight": 100 }
      ]
    }
  },

  "entities": {
    "Employee": {
      "fields": {
        "id": { "generator": "employee_id", "primary_key": true },
        "first_name": { "generator": "first_name" },
        "last_name": { "generator": "last_name" },
        "email": { "generator": "company_email" },
        "phone": { "generator": "phone" },
        "department_code": { "type": "template", "pattern": "{{department.code}}" },
        "hire_date": { "generator": "timestamp" }
      }
    }
  }
}

Note: The phone generator overrides the one from @phony/tr_TR. Generators from first_name, last_name are inherited from tr_TR, while timestamp is inherited from base.


Resolution Algorithm

When a generator or list is requested, Phony resolves it through the inheritance chain:

┌─────────────────────────────────────────────────────────────────────────┐
│           RESOLUTION ALGORITHM                                           │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Request: {{city}}                                                      │
│                                                                          │
│  Step 1: Check current package (@yourcompany/internal)                  │
│          ├─▶ Found? → Use it                                            │
│          └─▶ Not found? → Continue to Step 2                            │
│                                                                          │
│  Step 2: Check inherited package (@phony/tr_TR)                         │
│          ├─▶ Found? → Use it ✓                                          │
│          └─▶ Not found? → Continue to Step 3                            │
│                                                                          │
│  Step 3: Check base package (@phony/base)                               │
│          ├─▶ Found? → Use it                                            │
│          └─▶ Not found? → Error: Generator not found                    │
│                                                                          │
│  ═══════════════════════════════════════════════════════════════════════ │
│                                                                          │
│  EXAMPLES:                                                               │
│                                                                          │
│  {{city}}                                                               │
│  @yourcompany/internal: ✗ Not defined                                   │
│  @phony/tr_TR: ✓ Found → lists/geo/cities.json                         │
│                                                                          │
│  {{uuid}}                                                               │
│  @yourcompany/internal: ✗ Not defined                                   │
│  @phony/tr_TR: ✗ Not defined                                            │
│  @phony/base: ✓ Found → logic/uuid.json                                 │
│                                                                          │
│  {{employee_id}}                                                        │
│  @yourcompany/internal: ✓ Found → template                             │
│                                                                          │
│  {{phone}}                                                              │
│  @yourcompany/internal: ✓ Found → OVERRIDES tr_TR version              │
│                                                                          │
└─────────────────────────────────────────────────────────────────────────┘

Locale-Independent Data

Some data is marked as locale_independent and is shared across all locales:

json
{
  "generators": {
    "http_method": {
      "type": "list",
      "source": "lists/internet/http_methods.json",
      "locale_independent": true
    },
    "uuid": {
      "type": "logic",
      "algorithm": "uuid_v7",
      "locale_independent": true
    }
  }
}

When to use locale_independent:

Data TypeLocale Independent?Reason
HTTP methodsYesStandards don't vary
Country codesYesISO standard
UUIDsYesAlgorithm is universal
Person namesNoVary by language
CitiesNoVary by country
Address formatNoPatterns vary by country
Phone formatNoFormats vary by country

Multiple Locales in One Project

You can use multiple locales in a single schema:

json
{
  "$schema": "https://phony.cloud/schemas/pdl-1.0.json",
  "version": "1.0",
  "name": "International E-Commerce",
  "locale": "en_US",

  "imports": ["@phony/tr_TR", "@phony/de_DE"],

  "generators": {
    "turkish_name": {
      "type": "model",
      "source": "@phony/tr_TR/models/person/first_names.ngram"
    },
    "german_name": {
      "type": "model",
      "source": "@phony/de_DE/models/person/first_names.ngram"
    },
    "american_name": {
      "type": "model",
      "source": "models/person/first_names.ngram"
    }
  },

  "entities": {
    "User": {
      "fields": {
        "id": { "generator": "uuid" },
        "locale": {
          "type": "list",
          "source": "inline",
          "values": ["tr_TR", "de_DE", "en_US"]
        },
        "first_name": {
          "type": "template",
          "pattern": "{{switch(self.locale, tr_TR: turkish_name, de_DE: german_name, default: american_name)}}"
        }
      }
    }
  }
}

Creating New Locales

Step 1: Create Package Structure

bash
phony packages create my-locale --locale fr_FR --inherits @phony/base

Step 2: Train Models

bash
# Train from French name data
phony train french_names.txt -o my-locale/models/person/first_names.ngram

# Train from French company data
phony train french_companies.txt -o my-locale/models/business/company_names.ngram

Step 3: Add Lists

json
// my-locale/lists/geo/cities.json
[
  { "name": "Paris", "department": "75", "region": "Île-de-France" },
  { "name": "Lyon", "department": "69", "region": "Auvergne-Rhône-Alpes" },
  { "name": "Marseille", "department": "13", "region": "Provence-Alpes-Côte d'Azur" }
]

Step 4: Define Templates

json
{
  "generators": {
    "full_address": {
      "type": "template",
      "variants": [
        { "pattern": "{{number:1-200}} {{street_name}}, {{zip}} {{city}}", "weight": 100 }
      ]
    }
  }
}

Note: French address format is 42 Rue de Rivoli, 75001 Paris

Step 5: Build and Publish

bash
phony packages build my-locale
phony packages publish my-locale-1.0.0.phony

Best Practices

1. Use Inheritance

Good: Inherit and override only what's needed:

json
{
  "inherits": "@phony/tr_TR",
  "generators": {
    "phone": { "type": "template", "pattern": "..." }
  }
}

Bad: Duplicating everything from tr_TR instead of inheriting.

2. Mark Locale-Independent Data

Good: Explicit marking:

json
{
  "http_method": {
    "type": "list",
    "source": "lists/http_methods.json",
    "locale_independent": true
  }
}

Bad: Ambiguous without locale_independent flag.

3. Organize by Domain

lists/
├── geo/          # Geographic data
├── person/       # Person-related
├── commerce/     # E-commerce related
└── address/      # Address components

4. Use Metadata in Lists

json
// Good: Includes useful metadata
[
  { "name": "İstanbul", "plate": "34", "region": "Marmara", "population": 15840900 }
]

// Minimal: Just values (acceptable for simple cases)
["İstanbul", "Ankara", "İzmir"]

Phony Cloud Platform Specification