Skip to content

Locale System ​

Superseded — packaging & inheritance

The inherits field and phony packages create --inherits examples on this page are superseded by the git-based package manager: a locale package now lists what it builds on under dependencies, and its data merges via the locale-contribution model (domain > locale > base). The locale resolution concepts below still apply; treat inherits / manifest.json snippets as historical.

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.pgdl.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/pgdl-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.pgdl.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/pgdl-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/pgdl-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/pgdl-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 — Documentation & Specification