feat: localize country names in PDFs via symfony/intl (#681)

Ports #639 to 3.x — the original targets the now feature-frozen 2.x line.
Address::country_name now resolves the localized country name for the
current app locale via Symfony\Component\Intl\Countries, falling back to
the stored name on lookup failure. Adds symfony/intl + a unit test.

Co-authored-by: Lukas Selch <selchlukas@icloud.com>
This commit is contained in:
Darko Gjorgjijoski
2026-06-12 14:43:41 +02:00
committed by GitHub
parent acbb1f040c
commit 217deb0bf9
4 changed files with 161 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Symfony\Component\Intl\Countries;
class Address extends Model
{
@@ -18,9 +19,18 @@ class Address extends Model
public function getCountryNameAttribute(): ?string
{
$name = $this->country ? $this->country->name : null;
if (! $this->country) {
return null;
}
return $name;
try {
return Countries::getName(
$this->country->code,
app()->getLocale()
);
} catch (\Exception $e) {
return $this->country->name;
}
}
public function user(): BelongsTo

View File

@@ -28,6 +28,7 @@
"spatie/laravel-backup": "^10.0",
"spatie/laravel-medialibrary": "^11.11",
"symfony/http-client": "^7.3",
"symfony/intl": "^7.3",
"symfony/mailer": "^7.3",
"symfony/mailgun-mailer": "^7.3",
"symfony/postmark-mailer": "^7.3"

92
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bc1914fedd271f614d922d716ab0e3be",
"content-hash": "c1dac5a39ce45f742d64a45877782655",
"packages": [
{
"name": "aws/aws-crt-php",
@@ -6751,6 +6751,96 @@
],
"time": "2026-05-29T08:46:08+00:00"
},
{
"name": "symfony/intl",
"version": "v7.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/intl.git",
"reference": "7cfb7792d580dea833647420afd5f2f98df8457b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/intl/zipball/7cfb7792d580dea833647420afd5f2f98df8457b",
"reference": "7cfb7792d580dea833647420afd5f2f98df8457b",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"symfony/string": "<7.1"
},
"require-dev": {
"symfony/filesystem": "^6.4|^7.0|^8.0",
"symfony/var-exporter": "^6.4|^7.0|^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Intl\\": ""
},
"exclude-from-classmap": [
"/Tests/",
"/Resources/data/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
},
{
"name": "Eriksen Costa",
"email": "eriksen.costa@infranology.com.br"
},
{
"name": "Igor Wiedler",
"email": "igor@wiedler.ch"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides access to the localization data of the ICU library",
"homepage": "https://symfony.com",
"keywords": [
"i18n",
"icu",
"internationalization",
"intl",
"l10n",
"localization"
],
"support": {
"source": "https://github.com/symfony/intl/tree/v7.4.8"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-03-30T12:55:43+00:00"
},
{
"name": "symfony/mailer",
"version": "v7.4.12",

View File

@@ -0,0 +1,57 @@
<?php
use App\Models\Address;
use App\Models\Country;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Artisan;
beforeEach(function () {
Artisan::call('db:seed', ['--class' => 'DatabaseSeeder', '--force' => true]);
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
});
it('returns localized country name for German locale', function () {
App::setLocale('de');
$address = Address::factory()->create([
'country_id' => Country::where('code', 'DE')->first()->id,
]);
expect($address->country_name)->toBe('Deutschland');
});
it('returns localized country name for French locale', function () {
App::setLocale('fr');
$address = Address::factory()->create([
'country_id' => Country::where('code', 'DE')->first()->id,
]);
expect($address->country_name)->toBe('Allemagne');
});
it('returns null when address has no country', function () {
$address = Address::factory()->create([
'country_id' => null,
]);
expect($address->country_name)->toBeNull();
});
it('falls back to database name when intl lookup fails', function () {
App::setLocale('de');
// Create a country with a code that won't be found in ICU data
$country = new Country;
$country->timestamps = false;
$country->code = 'XX';
$country->name = 'Unknown Land';
$country->phonecode = 0;
$country->save();
$address = Address::factory()->create([
'country_id' => $country->id,
]);
expect($address->country_name)->toBe('Unknown Land');
});