laravel-locale-helper maintained by teknyo
Laravel Locale Helper
A simple Laravel package providing languages, timezones, currencies, date formats, first-day-of-week management, and number formatting.
Installation
composer require teknyo/laravel-locale-helper
Publish the config:
php artisan vendor:publish --tag=locale-helper-config
Usage
- Using the Facade
use Teknyo\LocaleHelper\Facades\LocaleHelper;
- Using Dependency Injection
use Teknyo\LocaleHelper\Services\LocaleService;
public function __construct(protected LocaleService $locale) {}
- Languages
// All languages (keyed by ISO 639-1 code)
$all = LocaleHelper::getLanguages();
// ['en' => ['name' => 'English', 'native' => 'English', 'rtl' => false], ...]
// Single language
LocaleHelper::getLanguage('fr');
// ['name' => 'French', 'native' => 'Français', 'rtl' => false]
// Simple name list for dropdowns
LocaleHelper::getLanguageNames(); // English names
LocaleHelper::getLanguageNames('native'); // Native names
- Timezones
// All timezones with offset metadata
LocaleHelper::getTimezones();
// Single timezone
LocaleHelper::getTimezone('America/New_York');
// ['identifier' => 'America/New_York', 'offset_label' => 'UTC-05:00', ...]
// Grouped by continent
LocaleHelper::getTimezonesGroupedByRegion();
// Ready for <select> dropdown
LocaleHelper::getTimezonesForSelect();
- Currencies
// All currencies
LocaleHelper::getCurrencies();
// Single currency
LocaleHelper::getCurrency('EUR');
// ['name' => 'Euro', 'symbol' => '€', 'decimals' => 2, 'symbol_first' => true]
// Just the symbol
LocaleHelper::getCurrencySymbol('GBP'); // '£'
// For dropdown
LocaleHelper::getCurrenciesForSelect();
- Date Formats
// All formats with labels and examples
LocaleHelper::getDateFormats();
// For dropdown
LocaleHelper::getDateFormatsForSelect();
// Format a date
LocaleHelper::formatDate('2026-06-29', 'd/m/Y'); // '29/06/2026'
LocaleHelper::formatDate(now()); // uses config default
- First Day of Week
// Get configured day (0=Sun .. 6=Sat)
LocaleHelper::getFirstDayOfWeek(); // 1 (Monday)
// Change at runtime
LocaleHelper::setFirstDayOfWeek(0);
// Get day name
LocaleHelper::getFirstDayOfWeekName(); // 'Sunday'
// Get ordered days starting from configured first day
LocaleHelper::getDaysOfWeek();
// [0 => 'Sunday', 1 => 'Monday', ..., 6 => 'Saturday']
// Auto-detect from locale
LocaleHelper::resolveFirstDayOfWeekForLocale('ar_SA'); // 6 (Saturday)
LocaleHelper::resolveFirstDayOfWeekForLocale('en_US'); // 0 (Sunday)
LocaleHelper::resolveFirstDayOfWeekForLocale('de'); // 1 (Monday)
- Number Formatting
// Basic number
LocaleHelper::formatNumber(1234567.891);
// '1,234,567.89'
// Custom separators (European style)
LocaleHelper::formatNumber(1234567.89, 2, ',', '.');
// '1.234.567,89'
// Currency (uses intl if available)
LocaleHelper::formatCurrency(1234.5, 'USD', 'en_US'); // '$1,234.50'
LocaleHelper::formatCurrency(1234.5, 'EUR', 'de_DE'); // '1.234,50 €'
// Percentage
LocaleHelper::formatPercentage(85.456, 1); // '85.5%'
// Parse formatted number back to float
LocaleHelper::parseNumber('1,234,567.89'); // 1234567.89
LocaleHelper::parseNumber('1.234.567,89', ',', '.'); // 1234567.89
- Countries
use Teknyo\LocaleHelper\Facades\CountryHelper;
// All countries
CountryHelper::getCountries();
// Single country
CountryHelper::getCountry('FR');
// ['name' => 'France', 'native' => 'France', 'phone' => '33', 'currency' => 'EUR', ...]
// Specific fields
CountryHelper::getCountryName('JP'); // 'Japan'
CountryHelper::getCountryName('JP', native: true); // '日本'
CountryHelper::getCountryFlag('BR'); // '🇧🇷'
CountryHelper::getCountryPhoneCode('DE'); // '49'
CountryHelper::getCountryCurrency('GB'); // 'GBP'
// Reverse lookups
CountryHelper::findByAlpha3('CAN'); // Canada data
CountryHelper::findByPhoneCode('1'); // ['US' => ..., 'CA' => ...]
// For dropdowns
CountryHelper::getCountriesForSelect();
// ['US' => '🇺🇸 United States (+1)', 'FR' => '🇫🇷 France (+33)', ...]
CountryHelper::getCountriesForSelect(withFlag: false, withPhone: false);
// ['US' => 'United States', 'FR' => 'France', ...]
- Phone Numbers
use Teknyo\LocaleHelper\Facades\PhoneHelper;
// Get codes
PhoneHelper::getPhoneCodes(); // ['US' => '1', 'GB' => '44', ...]
PhoneHelper::getPhoneCodesGrouped(); // ['1' => ['US', 'CA'], '44' => ['GB'], ...]
PhoneHelper::getPhoneCode('FR'); // '33'
// Dropdown
PhoneHelper::getPhoneCodesForSelect();
// ['US' => '🇺🇸 United States (+1)', ...]
// Formatting
PhoneHelper::formatE164('(201) 555-0123', 'US');
// '+12015550123'
PhoneHelper::formatInternational('2015550123', 'US');
// '+1 (201) 555-0123'
PhoneHelper::formatNational('+12015550123', 'US');
// '(201) 555-0123'
PhoneHelper::formatNational('0612345678', 'FR');
// '06 12 34 56 78'
PhoneHelper::formatNational('0791234567', 'GB');
// '07912 345678'
// Validation
PhoneHelper::validate('2015550123', 'US'); // true (10 digits)
PhoneHelper::validate('12345', 'US'); // false (too short)
PhoneHelper::validate('0612345678', 'FR'); // true (9 digits)
PhoneHelper::isE164('+12015550123'); // true
PhoneHelper::isE164('2015550123'); // false
// Guess country from full number
PhoneHelper::guessCountry('+33612345678'); // 'FR'
PhoneHelper::guessCountry('+442071234567'); // 'GB'
PhoneHelper::guessCountry('+14155552671'); // 'US' (first match)
Include/Exclude Usage
- Config - only expose a few currencies
// config/locale-helper.php
'currencies' => [
'include' => ['USD', 'EUR', 'GBP'],
'exclude' => [],
],
LocaleHelper::getCurrencies();
// → only USD, EUR, GBP
LocaleHelper::getCurrency('JPY');
// → null (filtered out)
LocaleHelper::getCurrenciesForSelect();
// → 3 options only
// But formatting still works — uses raw data internally
LocaleHelper::formatCurrency(1000, 'JPY', 'ja_JP');
// → '¥1,000'
- Config - exclude a few languages
'languages' => [
'include' => [],
'exclude' => ['la', 'sa', 'eo'],
],
LocaleHelper::getLanguageNames();
// → all languages EXCEPT Latin, Sanskrit, Esperanto
LocaleHelper::getLanguage('la');
// → null
- Config - exclude some timezones
'timezones' => [
'include' => [],
'exclude' => [
'Antarctica/McMurdo',
'Antarctica/South_Pole',
],
],
LocaleHelper::getTimezonesGroupedByRegion();
// → all regions, minus the two excluded zones
- Config - include/exclude countries and phone Restrict to EU countries only
'countries' => [
'include' => [
'AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR',
'HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK',
'SI','ES','SE',
],
],
'phones' => [
'include' => [
'AT','BE','BG','HR','CY','CZ','DK','EE','FI','FR','DE','GR',
'HU','IE','IT','LV','LT','LU','MT','NL','PL','PT','RO','SK',
'SI','ES','SE',
],
],
Exclude specific countries
'countries' => [
'exclude' => ['KP', 'IR', 'SY'],
],
Runtime Override
If you need to change filters at runtime, you can merge config on the fly:
config([
'locale-helper.currencies.include' => ['USD', 'AED'],
]);
// Rebuild the singleton so it picks up new config
app()->forgetInstance('locale-helper');
LocaleHelper::getCurrencies(); // now only USD + AED
Or expose a fluent helper on the service:
public function withCurrencies(array $codes): static
{
$clone = clone $this;
$clone->config['currencies']['include'] = $codes;
$clone->config['currencies']['exclude'] = [];
return $clone;
}
// Usage
$clone = LocaleHelper::withCurrencies(['USD', 'EUR']);
$clone->getCurrencies(); // only USD + EUR
Quick-Start Blade Example
{{-- Language selector --}}
<select name="language">
@foreach(\Teknyo\LocaleHelper\Facades\LocaleHelper::getLanguageNames('native') as $code => $name)
<option value="{{ $code }}" @selected(app()->getLocale() === $code)>
{{ $name }}
</option>
@endforeach
</select>
{{-- Timezone selector --}}
<select name="timezone">
@foreach(\Teknyo\LocaleHelper\Facades\LocaleHelper::getTimezonesForSelect() as $id => $label)
<option value="{{ $id }}">{{ $label }}</option>
@endforeach
</select>
{{-- Currency display --}}
<span>{{ \Teknyo\LocaleHelper\Facades\LocaleHelper::formatCurrency($order->total, 'USD') }}</span>
- Phone Input Integration
This guide explains how to implement a user-friendly phone number input that combines a country code selector with a local number input, ultimately submitting a standardized E.164 formatted phone number to your backend.
- The HTML Structure (Required for both methods)
Regardless of which JavaScript method you choose, you need the following HTML structure in your Blade template.
The
<select>element must include a data-phone-code attribute containing the numeric dialing code, which the JavaScript will use to construct the final E.164 number.
<div class="phone-input">
<select name="phone_country" id="phone_country">
@foreach(\Teknyo\LocaleHelper\Facades\PhoneHelper::getPhoneCodesForSelect() as $code => $label)
<option value="{{ $code }}" data-phone-code="{{ \Teknyo\LocaleHelper\Facades\CountryHelper::getCountryPhoneCode($code) }}">
{{ $label }}
</option>
@endforeach
</select>
<input type="tel" name="phone_number" id="phone_number" placeholder="Phone number">
</div>
Method 1: Using the @localhelperscripts Blade Directive (Recommended) The package provides a custom Blade directive that automatically injects the necessary JavaScript to handle the input, format the number, and inject a hidden field containing the final E.164 formatted number.
Basic Usage If your HTML elements use the default IDs (phone_country and phone_number), simply add the directive at the bottom of your form or page:
<form action="/submit" method="POST">
<!-- HTML Structure from above -->
@csrf
<!-- Injects the script with default settings -->
@localhelperscripts
<button type="submit">Submit</button>
</form>
What it does: Listens for changes on the select and input fields. Creates a hidden input named phone (by default). Populates it with the E.164 format (e.g., +12015550123) right before the form submits.
Advanced Usage (Custom IDs & Names) If you have multiple phone inputs on the same page (e.g., Billing and Shipping) or need to change the hidden field name, pass parameters to the directive:
@localhelperscripts('select_id', 'input_id', 'hidden_field_name')
- Example: Billing Phone
<select id="billing_phone_country">...</select>
<input type="tel" id="billing_phone_number">
@localhelperscripts('billing_phone_country', 'billing_phone_number', 'billing_phone')
- Example: Shipping Phone
<select id="shipping_phone_country">...</select>
<input type="tel" id="shipping_phone_number">
@localhelperscripts('shipping_phone_country', 'shipping_phone_number', 'shipping_phone')
Method 2: Manual Script Injection (Normal Script Way) If you prefer not to use the Blade directive, or if you are working in an environment where Blade directives aren't available (e.g., a standalone Vue/React component or a raw HTML file), you can inject the JavaScript manually.
Basic Implementation Add the following script block to your Blade template, typically inside an @push('scripts') block if you are using a layout that yields scripts at the bottom of the page.
@push('scripts')
<script>
(function() {
// 1. Define your element IDs and hidden field name
const SELECT_ID = 'phone_country';
const INPUT_ID = 'phone_number';
const HIDDEN_NAME = 'phone';
const selectEl = document.getElementById(SELECT_ID);
const inputEl = document.getElementById(INPUT_ID);
const form = selectEl ? selectEl.closest('form') : null;
if (!selectEl || !inputEl || !form) return;
// 2. Function to calculate and update the E.164 number
function updateHiddenField() {
const selectedOption = selectEl.options[selectEl.selectedIndex];
const code = selectedOption ? (selectedOption.dataset.phoneCode || '') : '';
const digits = inputEl.value.replace(/\D/g, '');
// Find existing hidden field or create a new one
let hidden = form.querySelector(`input[name="${HIDDEN_NAME}"]`);
if (!hidden) {
hidden = document.createElement('input');
hidden.type = 'hidden';
hidden.name = HIDDEN_NAME;
form.appendChild(hidden);
}
// Set the value (E.164 format)
hidden.value = code ? '+' + code + digits : digits;
}
// 3. Attach event listeners
form.addEventListener('submit', updateHiddenField); // Safety net on submit
inputEl.addEventListener('input', updateHiddenField); // Real-time update
selectEl.addEventListener('change', updateHiddenField); // Update on country change
// 4. Initial run (useful for validation error page reloads)
updateHiddenField();
})();
</script>
@endpush
Customizing the Manual Script To use this for a different set of inputs, simply change the constants at the top of the script:
const SELECT_ID = 'billing_phone_country';
const INPUT_ID = 'billing_phone_number';
const HIDDEN_NAME = 'billing_phone';
Handling the Data in Laravel (Backend) Regardless of which frontend method you use, the form will submit a hidden field containing the fully formatted E.164 phone number. Here is how to handle it in your Laravel Controller:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Teknyo\LocaleHelper\Facades\PhoneHelper;
class UserController extends Controller
{
public function store(Request $request)
{
$request->validate([
'phone_country' => 'required|string|max:2',
'phone_number' => 'required|string',
'phone' => 'required|string', // The hidden E.164 field
]);
$e164Number = $request->input('phone');
$countryCode = $request->input('phone_country');
// Optional: Validate the phone number length/format using the package
if (! PhoneHelper::validate($e164Number, $countryCode)) {
return back()->withErrors(['phone' => 'The phone number format is invalid for the selected country.']);
}
// Optional: Verify it's strictly E.164
if (! PhoneHelper::isE164($e164Number)) {
return back()->withErrors(['phone' => 'Invalid phone format.']);
}
// Save the user
// User::create(['phone' => $e164Number, ...]);
return redirect()->back()->with('success', 'Phone number saved!');
}
}
License
MIT