Skip to main content

Phone number + timezone → country, currency, language, IANA timezone, UTC offset, and full formatting metadata. 191 countries, 590+ IANA zones, 1,590+ area codes, flat FullLocaleResult, BCP 47 language tags. Zero runtime dependencies.

Project description

tala-locale

CI PyPI Version Python Versions License: MIT Coverage

Phone number + timezone → country, currency, language, IANA timezone, formatting, VAT, and more. Zero runtime dependencies.

from tala_locale import infer_full_locale

r = infer_full_locale("+2348012345678")
r.country          # "NG"
r.currency         # "NGN"
r.language         # "en-NG"   ← BCP 47, not bare "en"
r.languages        # ("en-NG", "ha-NG", "yo-NG", "ig-NG", "pcm-NG")
r.timezone         # "Africa/Lagos"
r.utc_offset_hours # 1.0
r.currency_symbol  # "₦"
r.vat_rate         # 0.075     ← 7.5%
r.date_format      # "%d/%m/%Y"
r.confidence       # 1.0

Why tala-locale?

Any application that collects a phone number can instantly know where a user is from — country, currency, timezone, language, number formatting, VAT rate, RTL direction — all from one call, with no GPS, no IP lookup, no user forms, and zero runtime dependencies.

What you want What you get
Pre-fill currency for pricing r.currency"GBP", "NGN", "AED"
Show onboarding in the user's language r.language"en-NG", "fr-SN", "ar-MA"
Know ALL languages spoken in a country r.languages("ar-MA", "fr-MA", "tzm-MA", "zgh-MA")
Format money correctly r.currency_symbol, r.decimal_sep, r.currency_before
Apply correct VAT rate r.vat_rate0.075 (7.5%), 0.20 (20%)
Right-to-left layout r.rtlTrue for Arabic, Hebrew, Persian, Urdu
Correct date format r.date_format"%d/%m/%Y" (Nigeria), "%Y-%m-%d" (Sweden)
Disambiguate +1 (US vs Canada) Area code resolution → +1-416 → Canada at 0.95 confidence
Route to the right support team r.country"KE", "ZA", "IN"
Skip "where are you from?" Infer it silently at signup
Convert times to local format_local_datetime(utc_dt, "NG")"14/06/2025 13:30"

Works with any messaging platform or application that handles phone numbers: WhatsApp, Telegram, SMS, SaaS onboarding, e-commerce checkout, fintech KYC, fraud detection, CRM enrichment, AI assistants.


Installation

pip install tala-locale

Pure Python. Zero runtime dependencies. Python 3.10+.

On Windows only: installs tzdata (IANA timezone database). On Linux/Mac the OS provides it. Lambda (Linux) = truly zero deps at runtime.


Quick start

Basic: country, currency, language from a phone number

from tala_locale import infer_locale

result = infer_locale("+447911123456")
result.country   # "GB"
result.currency  # "GBP"
result.language  # "en-GB"

# Unpack like a tuple
country, currency, language = infer_locale("+254712345678")
# country="KE", currency="KES", language="sw-KE"

# Check before using
if result.is_known():
    set_user_currency(result.currency)
else:
    ask_user_for_currency()

Full: timezone + formatting + confidence + all languages

from tala_locale import infer_full_locale

# Nigeria — single-country prefix, full confidence
r = infer_full_locale("+2348012345678")
r.country          # "NG"
r.language         # "en-NG"
r.languages        # ("en-NG", "ha-NG", "yo-NG", "ig-NG", "pcm-NG")
r.timezone         # "Africa/Lagos"
r.utc_offset_hours # 1.0
r.currency_symbol  # "₦"
r.vat_rate         # 0.075
r.date_format      # "%d/%m/%Y"
r.rtl              # False
r.confidence       # 1.0

# Morocco — Arabic primary, but French + Berber also in use
r = infer_full_locale("+212612345678")
r.language         # "ar-MA"
r.languages        # ("ar-MA", "fr-MA", "tzm-MA", "zgh-MA")
r.rtl              # True
r.date_format      # "%d/%m/%Y"

# UAE — Gulf Arabic, not Egyptian Arabic, not Levantine Arabic
r = infer_full_locale("+971501234567")
r.language         # "ar-AE"
r.languages        # ("ar-AE", "en-AE")

# Lebanon — genuinely trilingual in business
r = infer_full_locale("+96170123456")
r.languages        # ("ar-LB", "fr-LB", "en-LB")

# South Africa — 11 official languages
r = infer_full_locale("+27821234567")
r.languages        # ("en-ZA", "zu-ZA", "xh-ZA", "af-ZA", "nso-ZA", ...)

# Disambiguating +1 (US vs Canada) — area code resolution, no browser needed
r = infer_full_locale("+14165551234")  # 416 = Toronto
r.country          # "CA"
r.timezone         # "America/Toronto"
r.confidence       # 0.95

# With browser timezone as additional signal
r = infer_full_locale("+14165551234", browser_timezone="America/Toronto")
r.country          # "CA"
r.confidence       # 0.95

# Timezone-only fallback (no phone given)
r = infer_full_locale("", browser_timezone="Africa/Lagos")
r.country          # "NG"
r.confidence       # 0.8

Phone format tolerance

tala-locale accepts phone numbers in any common format:

infer_locale("+234 801 234 5678")        # spaces
infer_locale("+234-801-234-5678")        # dashes
infer_locale("(234) 801 234 5678")       # parentheses
infer_locale("2348012345678")            # bare digits, no +
infer_locale("+2348012345678")           # E.164 standard
infer_locale("whatsapp:+2348012345678")  # WhatsApp format

All of the above return LocaleResult(country='NG', currency='NGN', language='en-NG').


Language tags: BCP 47, not bare ISO 639-1

tala-locale returns country-qualified BCP 47 language tags, not bare ISO 639-1 codes.

This matters because:

  • ar-EG (Egyptian Arabic) ≠ ar-SA (Gulf Arabic) ≠ ar-MA (Moroccan Darija) ≠ ar-LY (Libyan Arabic)
  • en-NG (Nigerian English) ≠ en-GBen-USen-AUen-IN
  • pt-BR (Brazilian Portuguese) ≠ pt-PT (European Portuguese)
  • zh-CN (Simplified) ≠ zh-HK / zh-TW (Traditional)

When you pass a language tag to an LLM, TTS engine, or translation service, the dialect signal is what produces the correct register, vocabulary, and cultural context.

The languages tuple lists all significant languages for a country, primary first. Use it when you need to:

  • Offer a language selector pre-populated with the right options
  • Route a customer message to the right support agent
  • Prompt an LLM to respond in any of the customer's languages
# India — 11 languages listed
r = infer_full_locale("+919812345678")
r.languages
# ("en-IN", "hi-IN", "bn-IN", "te-IN", "mr-IN", "ta-IN", "ur-IN",
#  "gu-IN", "kn-IN", "ml-IN", "pa-IN")

# Switzerland — 4 official languages
from tala_locale import get_extended
ext = get_extended("CH")
ext.languages  # ("de-CH", "fr-CH", "it-CH", "rm-CH")

# Paraguay — genuinely bilingual: Spanish + Guaraní
ext = get_extended("PY")
ext.languages  # ("es-PY", "gn-PY")

Monetary formatting

from tala_locale import format_amount

format_amount(1234.5, "NG")  # "₦1,234.50"
format_amount(1234.5, "DE")  # "1.234,50 €"
format_amount(1234.5, "FR")  # "1 234,50 €"
format_amount(1234.5, "US")  # "$1,234.50"
format_amount(1234.5, "ZA")  # "R 1 234.50"
format_amount(1234.5, "JP")  # "¥1,234.50"

Datetime localisation

from datetime import datetime, timezone
from tala_locale import format_local_datetime, get_local_datetime

utc = datetime(2025, 6, 14, 11, 30, 0, tzinfo=timezone.utc)

format_local_datetime(utc, "NG")  # "14/06/2025 12:30"   (WAT = UTC+1)
format_local_datetime(utc, "US")  # "06/14/2025 07:30"   (EDT = UTC-4)
format_local_datetime(utc, "DE")  # "14.06.2025 13:30"   (CEST = UTC+2)
format_local_datetime(utc, "SE")  # "2025-06-14 13:30"   (CEST = UTC+2)
format_local_datetime(utc, "SA")  # "14/06/2025 14:30"   (AST = UTC+3)

# Date only
format_local_datetime(utc, "NG", include_time=False)  # "14/06/2025"

Area code disambiguation (+1 US vs Canada)

tala-locale includes 1,590+ area codes across 27 calling code regions. For shared prefixes like +1, it uses the area code to identify the country without needing a browser timezone:

r = infer_full_locale("+14165551234")   # 416 = Toronto
r.country    # "CA"
r.timezone   # "America/Toronto"
r.confidence # 0.95

r = infer_full_locale("+12125551234")   # 212 = New York City
r.country    # "US"
r.timezone   # "America/New_York"
r.confidence # 0.95

Confidence levels:

  • 1.0 — unambiguous prefix (single-country calling code)
  • 0.95 — area code disambiguated (e.g. +1-416 → CA)
  • 0.9 — browser timezone disambiguated
  • 0.8 — timezone-only (no phone number)
  • 0.0 — unknown

API reference

infer_locale(phone_number) → LocaleResult

Fast lookup. Returns (country, currency, language). All None if unknown.

infer_full_locale(phone_number, *, browser_timezone=None) → FullLocaleResult

Full inference. 19-field named tuple covering everything below.

class FullLocaleResult

Field Type Example
country str | None "NG"
currency str | None "NGN"
language str | None "en-NG" (BCP 47 primary)
languages tuple | None ("en-NG", "ha-NG", "yo-NG", "ig-NG", "pcm-NG")
timezone str | None "Africa/Lagos"
utc_offset_hours float | None 1.0
timezones tuple | None All IANA zones for the country
extended ExtendedLocale | None Full formatting object
confidence float 1.0 / 0.95 / 0.9 / 0.8 / 0.0
currency_symbol str | None "₦"
currency_before bool | None True (symbol before amount)
decimal_sep str | None "."
thousands_sep str | None ","
vat_rate float | None 0.075 (= 7.5%)
vat_name str | None "VAT", "TVA", "MwSt.", "GST"
date_format str | None "%d/%m/%Y"
rtl bool | None False
week_start int | None 0 = Monday, 6 = Sunday
bcp47 str | None "en-NG" (same as language)

class ExtendedLocale

Available via r.extended or get_extended("NG"). Same fields as above plus languages tuple.

Other functions

infer_country(phone)                     # → str | None
infer_currency(phone)                    # → str | None
infer_language(phone)                    # → str | None  (BCP 47)
is_supported(phone)                      # → bool
infer_country_from_timezone(tz)          # → str | None
infer_timezone(country_code)             # → str | None  (primary IANA)
infer_timezones(country_code)            # → tuple[str, ...]  (all IANA)
get_utc_offset(country_code_or_tz)       # → float | None
get_local_datetime(utc_dt, country_or_tz)     # → datetime (aware)
format_local_datetime(utc_dt, country_or_tz)  # → str
format_amount(value, country_code)            # → str
get_extended(country_code)               # → ExtendedLocale | None
supported_countries()                    # → list[dict]

Coverage

191 countries/territories across all regions:

Region Countries
West Africa Nigeria, Ghana, Senegal, Côte d'Ivoire, Mali, Burkina Faso, and 10 more
East Africa Kenya, Tanzania, Uganda, Ethiopia, Rwanda, Somalia, and 4 more
North Africa Egypt, Morocco, Algeria, Tunisia, Libya, Sudan
Southern Africa South Africa (11 official languages), Zambia, Zimbabwe, Botswana, and 5 more
Central Africa DRC, Cameroon, Angola, Gabon, and 5 more
Indian Ocean Madagascar, Mauritius, Seychelles, Comoros
Western Europe UK, France, Germany, Spain, Italy, Netherlands, Switzerland, and 10 more
Eastern Europe Russia, Ukraine, Poland, Czech Republic, and 15 more
North America USA, Canada (area code disambiguation), Mexico
Caribbean Trinidad, Jamaica, Barbados, Dominican Republic, Haiti, and 8 more
Central America Guatemala, Costa Rica, Panama, and 4 more
South America Brazil, Argentina, Colombia, Chile, Peru, Bolivia, Paraguay (Guaraní), and 4 more
Middle East UAE, Saudi Arabia, Israel, Turkey, Iran, Lebanon (trilingual), and 8 more
South Asia India (11 languages), Pakistan, Bangladesh, Sri Lanka, Nepal, Afghanistan
East Asia China, Japan, South Korea, Hong Kong, Macao, Taiwan, Mongolia
Southeast Asia Singapore (4 official), Indonesia, Malaysia, Thailand, Philippines, Vietnam, and 3 more
Central Asia Uzbekistan, Kazakhstan, Georgia, Armenia, Azerbaijan, and 3 more
Oceania Australia, New Zealand, Fiji, Papua New Guinea, and 10 more

How prefix matching works

tala-locale uses longest-prefix-match to handle overlapping calling codes:

infer_locale("18681234567")   # +1868 → Trinidad (TT), not USA (+1)
infer_locale("18761234567")   # +1876 → Jamaica (JM), not USA (+1)
infer_locale("85291234567")   # +852  → Hong Kong (HK)
infer_locale("8801712345678") # +880  → Bangladesh (BD)

Prefixes are sorted at import time — every lookup is O(n) on the fixed prefix list.


Handling unknown numbers

When the prefix is not recognised, all fields are None. Never assume a default:

result = infer_locale(phone)

if result.is_known():
    create_account(country=result.country, currency=result.currency)
else:
    prompt_user_for_country_and_currency()

Contributing

git clone https://github.com/rvethllc/tala-locale
cd tala-locale
pip install -e ".[dev]"
pytest
  • Missing a country? Add an entry to src/tala_locale/_data.py and _extended_data.py with a test.
  • Wrong VAT rate or language list? One-line fix + test.
  • New area code region? See scripts/generate_area_data.py for the build-time generator.

Versioning

Follows Semantic Versioning.

Version What changed
3.0.0 languages tuple (all significant languages per country, BCP 47), 1,590+ area codes for CA/US disambiguation, flat FullLocaleResult (direct field access), BCP 47 primary language tags ("en-NG" not "en"), 191-country extended dataset
2.0.0 Timezone support, infer_full_locale, UTC offset, datetime localisation, monetary formatting
1.0.0 infer_locale, 191 countries, ISO 4217 currencies

License

MIT — see LICENSE for details.


Built by RVETH — makers of TALA, the AI operating system for global enterprise.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tala_locale-3.0.0.tar.gz (67.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tala_locale-3.0.0-py3-none-any.whl (54.6 kB view details)

Uploaded Python 3

File details

Details for the file tala_locale-3.0.0.tar.gz.

File metadata

  • Download URL: tala_locale-3.0.0.tar.gz
  • Upload date:
  • Size: 67.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tala_locale-3.0.0.tar.gz
Algorithm Hash digest
SHA256 06921e12ef0dc1ba9feba604b43558278131e562ed06f8f207543efb92ed0dc7
MD5 ed58f42659bb2941840e2b75bbb31427
BLAKE2b-256 2c1db0059dc5a1ee6dc4c2c0aa7a057e6c34f3f9b73827fe99bb3a1f7e3ae60e

See more details on using hashes here.

Provenance

The following attestation bundles were made for tala_locale-3.0.0.tar.gz:

Publisher: ci.yml on rvethllc/tala-locale

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tala_locale-3.0.0-py3-none-any.whl.

File metadata

  • Download URL: tala_locale-3.0.0-py3-none-any.whl
  • Upload date:
  • Size: 54.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for tala_locale-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fe67e81b3ae7fe4f6cdfbdb1f843ae06ef9201a2a4c087538861a488dc4bff27
MD5 415ec130538105e52d4b030675fd12ca
BLAKE2b-256 95d6889d85b3510b175a5927a6acc1c94bb6633569523cda95089594e5421b80

See more details on using hashes here.

Provenance

The following attestation bundles were made for tala_locale-3.0.0-py3-none-any.whl:

Publisher: ci.yml on rvethllc/tala-locale

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page