[PHP]nl_langinfo関数とは?ロケール情報を取得する国際化対応の決定版

PHP

はじめに

多言語対応のWebアプリケーションを開発する際に、現在のロケール設定に応じた日付形式、通貨記号、数値区切り文字などの情報を動的に取得したいことはありませんか?PHPのnl_langinfo関数は、現在設定されているロケールから様々な地域固有の情報を取得できる強力な関数です。

この記事では、nl_langinfo関数の基本的な使い方から実践的な国際化対応まで、詳しく解説していきます。

nl_langinfo関数とは?

nl_langinfoは「Native Language Information」の略で、現在のロケール設定に基づいて地域固有の書式情報を取得する関数です。日付形式、時刻形式、通貨記号、数値区切り文字、文字エンコーディングなどの情報を動的に取得できます。

基本的な構文

string nl_langinfo(int $item)

パラメータ

  • $item: 取得したい情報の種類を表す定数

戻り値

  • 指定された項目の文字列値、または失敗時はFALSE

前提条件

この関数を使用するには、事前にsetlocale()でロケールを設定する必要があります。

<?php
// ロケールを設定してからnl_langinfoを使用
setlocale(LC_ALL, 'ja_JP.UTF-8');
echo nl_langinfo(D_FMT); // 日付形式を取得
?>

利用可能な定数

時間と日付関連

<?php
// 日本語ロケールを設定
setlocale(LC_TIME, 'ja_JP.UTF-8');

echo "=== 時間・日付関連 ===\n";

// 日付・時刻形式
echo "日付時刻形式 (D_T_FMT): " . nl_langinfo(D_T_FMT) . "\n";
echo "日付形式 (D_FMT): " . nl_langinfo(D_FMT) . "\n";
echo "時刻形式 (T_FMT): " . nl_langinfo(T_FMT) . "\n";
echo "AM/PM形式時刻 (T_FMT_AMPM): " . nl_langinfo(T_FMT_AMPM) . "\n";

// 月名(完全形)
echo "\n月名(完全形):\n";
$months = [MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, 
          MON_7, MON_8, MON_9, MON_10, MON_11, MON_12];

foreach ($months as $index => $constant) {
    echo ($index + 1) . "月: " . nl_langinfo($constant) . "\n";
}

// 月名(短縮形)
echo "\n月名(短縮形):\n";
$abbrMonths = [ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6,
               ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12];

foreach ($abbrMonths as $index => $constant) {
    echo ($index + 1) . "月: " . nl_langinfo($constant) . "\n";
}

// 曜日名(完全形)
echo "\n曜日名(完全形):\n";
$days = [DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7];
$dayNames = ['月', '火', '水', '木', '金', '土', '日'];

foreach ($days as $index => $constant) {
    echo $dayNames[$index] . "曜日: " . nl_langinfo($constant) . "\n";
}

// 曜日名(短縮形)
echo "\n曜日名(短縮形):\n";
$abbrDays = [ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7];

foreach ($abbrDays as $index => $constant) {
    echo $dayNames[$index] . "曜日: " . nl_langinfo($constant) . "\n";
}

// AM/PM
echo "\nAM/PM:\n";
echo "午前: " . nl_langinfo(AM_STR) . "\n";
echo "午後: " . nl_langinfo(PM_STR) . "\n";
?>

数値と通貨関連

<?php
// 日本語ロケールを設定
setlocale(LC_MONETARY | LC_NUMERIC, 'ja_JP.UTF-8');

echo "=== 数値・通貨関連 ===\n";

// 数値形式
echo "小数点記号: '" . nl_langinfo(RADIXCHAR) . "'\n";
echo "千の位区切り: '" . nl_langinfo(THOUSEP) . "'\n";

// 通貨関連
echo "\n通貨関連:\n";
echo "通貨記号: " . nl_langinfo(CRNCYSTR) . "\n";

// アメリカのロケールも試してみる
echo "\n=== アメリカ英語の場合 ===\n";
setlocale(LC_MONETARY | LC_NUMERIC, 'en_US.UTF-8');

echo "小数点記号: '" . nl_langinfo(RADIXCHAR) . "'\n";
echo "千の位区切り: '" . nl_langinfo(THOUSEP) . "'\n";
echo "通貨記号: " . nl_langinfo(CRNCYSTR) . "\n";
?>

文字セット関連

<?php
echo "=== 文字セット関連 ===\n";

// 様々なロケールでの文字セット情報
$locales = ['ja_JP.UTF-8', 'en_US.UTF-8', 'de_DE.UTF-8', 'fr_FR.UTF-8'];

foreach ($locales as $locale) {
    if (setlocale(LC_CTYPE, $locale)) {
        echo "$locale:\n";
        echo "  文字セット: " . nl_langinfo(CODESET) . "\n";
    } else {
        echo "$locale: 設定に失敗\n";
    }
}
?>

実践的な使用例

1. 多言語対応の日付フォーマッター

<?php
class InternationalDateFormatter {
    private $locale;
    private $originalLocale;
    
    public function __construct($locale = null) {
        $this->originalLocale = setlocale(LC_TIME, 0);
        if ($locale) {
            $this->setLocale($locale);
        }
    }
    
    public function __destruct() {
        if ($this->originalLocale) {
            setlocale(LC_TIME, $this->originalLocale);
        }
    }
    
    public function setLocale($locale) {
        $this->locale = $locale;
        if (!setlocale(LC_TIME, $locale)) {
            throw new InvalidArgumentException("ロケール '$locale' の設定に失敗しました");
        }
        return $this;
    }
    
    public function formatDate($timestamp = null, $format = 'full') {
        if ($timestamp === null) {
            $timestamp = time();
        }
        
        switch ($format) {
            case 'full':
                $formatString = nl_langinfo(D_T_FMT);
                break;
            case 'date_only':
                $formatString = nl_langinfo(D_FMT);
                break;
            case 'time_only':
                $formatString = nl_langinfo(T_FMT);
                break;
            case 'time_ampm':
                $formatString = nl_langinfo(T_FMT_AMPM);
                break;
            default:
                $formatString = $format;
        }
        
        return strftime($formatString, $timestamp);
    }
    
    public function getMonthNames($abbreviated = false) {
        $months = [];
        $constants = $abbreviated ? 
            [ABMON_1, ABMON_2, ABMON_3, ABMON_4, ABMON_5, ABMON_6,
             ABMON_7, ABMON_8, ABMON_9, ABMON_10, ABMON_11, ABMON_12] :
            [MON_1, MON_2, MON_3, MON_4, MON_5, MON_6,
             MON_7, MON_8, MON_9, MON_10, MON_11, MON_12];
        
        foreach ($constants as $index => $constant) {
            $months[$index + 1] = nl_langinfo($constant);
        }
        
        return $months;
    }
    
    public function getDayNames($abbreviated = false) {
        $days = [];
        $constants = $abbreviated ?
            [ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7] :
            [DAY_1, DAY_2, DAY_3, DAY_4, DAY_5, DAY_6, DAY_7];
        
        foreach ($constants as $index => $constant) {
            $days[$index + 1] = nl_langinfo($constant);
        }
        
        return $days;
    }
    
    public function getAmPmStrings() {
        return [
            'am' => nl_langinfo(AM_STR),
            'pm' => nl_langinfo(PM_STR)
        ];
    }
    
    public function generateCalendar($year, $month) {
        $firstDay = mktime(0, 0, 0, $month, 1, $year);
        $lastDay = mktime(0, 0, 0, $month + 1, 0, $year);
        $daysInMonth = date('j', $lastDay);
        $startDayOfWeek = date('N', $firstDay);
        
        $monthNames = $this->getMonthNames();
        $dayNames = $this->getDayNames(true);
        
        $calendar = [];
        $calendar['title'] = $monthNames[$month] . ' ' . $year;
        $calendar['headers'] = array_values($dayNames);
        
        // カレンダーの日付配列を生成
        $calendar['dates'] = [];
        $currentWeek = array_fill(0, 7, '');
        
        // 最初の週の空白を設定
        for ($i = 1; $i < $startDayOfWeek; $i++) {
            $currentWeek[$i - 1] = '';
        }
        
        // 日付を配置
        for ($day = 1; $day <= $daysInMonth; $day++) {
            $dayOfWeek = ($startDayOfWeek + $day - 2) % 7;
            $currentWeek[$dayOfWeek] = $day;
            
            // 週の最後の日または月の最後の日
            if ($dayOfWeek === 6 || $day === $daysInMonth) {
                $calendar['dates'][] = $currentWeek;
                $currentWeek = array_fill(0, 7, '');
            }
        }
        
        return $calendar;
    }
}

// 使用例
echo "=== 多言語日付フォーマッター ===\n";

$formatter = new InternationalDateFormatter();

$locales = [
    'ja_JP.UTF-8' => '日本語',
    'en_US.UTF-8' => '英語(アメリカ)',
    'de_DE.UTF-8' => 'ドイツ語',
    'fr_FR.UTF-8' => 'フランス語'
];

$currentTime = time();

foreach ($locales as $locale => $name) {
    try {
        echo "\n--- $name ($locale) ---\n";
        $formatter->setLocale($locale);
        
        echo "完全形式: " . $formatter->formatDate($currentTime, 'full') . "\n";
        echo "日付のみ: " . $formatter->formatDate($currentTime, 'date_only') . "\n";
        echo "時刻のみ: " . $formatter->formatDate($currentTime, 'time_only') . "\n";
        
        $months = $formatter->getMonthNames(true);
        echo "月名(短縮): " . implode(', ', array_slice($months, 0, 6)) . "...\n";
        
        $days = $formatter->getDayNames(true);
        echo "曜日名(短縮): " . implode(', ', $days) . "\n";
        
    } catch (Exception $e) {
        echo "エラー: " . $e->getMessage() . "\n";
    }
}

// カレンダー生成例
echo "\n=== カレンダー生成 ===\n";
try {
    $formatter->setLocale('ja_JP.UTF-8');
    $calendar = $formatter->generateCalendar(2024, 3);
    
    echo $calendar['title'] . "\n";
    echo implode('  ', $calendar['headers']) . "\n";
    echo str_repeat('-', 20) . "\n";
    
    foreach ($calendar['dates'] as $week) {
        foreach ($week as $day) {
            echo sprintf('%3s', $day);
        }
        echo "\n";
    }
} catch (Exception $e) {
    echo "カレンダー生成エラー: " . $e->getMessage() . "\n";
}
?>

2. 多言語対応の数値・通貨フォーマッター

<?php
class InternationalNumberFormatter {
    private $locale;
    private $originalLocale;
    
    public function __construct($locale = null) {
        $this->originalLocale = setlocale(LC_NUMERIC | LC_MONETARY, 0);
        if ($locale) {
            $this->setLocale($locale);
        }
    }
    
    public function __destruct() {
        if ($this->originalLocale) {
            setlocale(LC_NUMERIC | LC_MONETARY, $this->originalLocale);
        }
    }
    
    public function setLocale($locale) {
        $this->locale = $locale;
        if (!setlocale(LC_NUMERIC | LC_MONETARY, $locale)) {
            throw new InvalidArgumentException("ロケール '$locale' の設定に失敗しました");
        }
        return $this;
    }
    
    public function formatNumber($number, $decimals = null) {
        $radixChar = nl_langinfo(RADIXCHAR);
        $thousandsSep = nl_langinfo(THOUSEP);
        
        if ($decimals === null) {
            // 自動的に小数点以下の桁数を決定
            $decimals = (floor($number) != $number) ? 2 : 0;
        }
        
        $formatted = number_format($number, $decimals, $radixChar, $thousandsSep);
        
        return $formatted;
    }
    
    public function formatCurrency($amount, $showSymbol = true) {
        $currencyInfo = $this->getCurrencyInfo();
        
        $formattedAmount = $this->formatNumber($amount, 2);
        
        if ($showSymbol && $currencyInfo['symbol']) {
            // 通貨記号の位置を決定
            if ($currencyInfo['position'] === 'before') {
                return $currencyInfo['symbol'] . $formattedAmount;
            } else {
                return $formattedAmount . $currencyInfo['symbol'];
            }
        }
        
        return $formattedAmount;
    }
    
    private function getCurrencyInfo() {
        $crncyStr = nl_langinfo(CRNCYSTR);
        
        // 通貨文字列の解析
        if (empty($crncyStr)) {
            return ['symbol' => '$', 'position' => 'before'];
        }
        
        // 最初の文字が'-'なら前置、'+'なら後置
        $position = (substr($crncyStr, 0, 1) === '-') ? 'before' : 'after';
        $symbol = substr($crncyStr, 1);
        
        return ['symbol' => $symbol, 'position' => $position];
    }
    
    public function formatPercentage($value, $decimals = 1) {
        $radixChar = nl_langinfo(RADIXCHAR);
        $percentage = $value * 100;
        
        $formatted = number_format($percentage, $decimals, $radixChar, '');
        return $formatted . '%';
    }
    
    public function parseNumber($formattedNumber) {
        $radixChar = nl_langinfo(RADIXCHAR);
        $thousandsSep = nl_langinfo(THOUSEP);
        
        // 千の位区切り文字を除去
        $cleaned = str_replace($thousandsSep, '', $formattedNumber);
        
        // 小数点記号を標準の'.'に変換
        if ($radixChar !== '.') {
            $cleaned = str_replace($radixChar, '.', $cleaned);
        }
        
        return floatval($cleaned);
    }
    
    public function getLocaleInfo() {
        return [
            'locale' => $this->locale,
            'decimal_point' => nl_langinfo(RADIXCHAR),
            'thousands_separator' => nl_langinfo(THOUSEP),
            'currency_info' => $this->getCurrencyInfo(),
            'charset' => nl_langinfo(CODESET)
        ];
    }
}

// 使用例
echo "=== 多言語数値フォーマッター ===\n";

$formatter = new InternationalNumberFormatter();
$testNumber = 1234567.89;
$testCurrency = 9876.54;

$locales = [
    'ja_JP.UTF-8' => '日本語',
    'en_US.UTF-8' => '英語(アメリカ)',
    'de_DE.UTF-8' => 'ドイツ語',
    'fr_FR.UTF-8' => 'フランス語'
];

foreach ($locales as $locale => $name) {
    try {
        echo "\n--- $name ($locale) ---\n";
        $formatter->setLocale($locale);
        
        echo "数値: " . $formatter->formatNumber($testNumber) . "\n";
        echo "通貨: " . $formatter->formatCurrency($testCurrency) . "\n";
        echo "パーセント: " . $formatter->formatPercentage(0.1234) . "\n";
        
        $info = $formatter->getLocaleInfo();
        echo "小数点: '{$info['decimal_point']}'\n";
        echo "千の位区切り: '{$info['thousands_separator']}'\n";
        echo "通貨記号: '{$info['currency_info']['symbol']}'\n";
        
    } catch (Exception $e) {
        echo "エラー: " . $e->getMessage() . "\n";
    }
}

// 数値のパースのテスト
echo "\n=== 数値のパーステスト ===\n";
try {
    $formatter->setLocale('de_DE.UTF-8'); // ドイツ語(カンマが小数点)
    
    $germanNumber = $formatter->formatNumber(1234.56);
    echo "ドイツ語形式: $germanNumber\n";
    
    $parsed = $formatter->parseNumber($germanNumber);
    echo "パース結果: $parsed\n";
    
} catch (Exception $e) {
    echo "パースエラー: " . $e->getMessage() . "\n";
}
?>

3. ロケール対応のWebアプリケーション

<?php
class LocaleAwareApplication {
    private $supportedLocales = [
        'ja' => 'ja_JP.UTF-8',
        'en' => 'en_US.UTF-8',
        'de' => 'de_DE.UTF-8',
        'fr' => 'fr_FR.UTF-8'
    ];
    
    private $currentLocale;
    private $dateFormatter;
    private $numberFormatter;
    
    public function __construct($language = 'en') {
        $this->setLanguage($language);
    }
    
    public function setLanguage($language) {
        if (!isset($this->supportedLocales[$language])) {
            throw new InvalidArgumentException("サポートされていない言語: $language");
        }
        
        $locale = $this->supportedLocales[$language];
        
        if (!setlocale(LC_ALL, $locale)) {
            throw new RuntimeException("ロケール設定に失敗: $locale");
        }
        
        $this->currentLocale = $language;
        $this->dateFormatter = new InternationalDateFormatter($locale);
        $this->numberFormatter = new InternationalNumberFormatter($locale);
        
        return $this;
    }
    
    public function getCurrentLocale() {
        return $this->currentLocale;
    }
    
    public function getAvailableLanguages() {
        return array_keys($this->supportedLocales);
    }
    
    public function formatDate($timestamp = null, $format = 'full') {
        return $this->dateFormatter->formatDate($timestamp, $format);
    }
    
    public function formatNumber($number, $decimals = null) {
        return $this->numberFormatter->formatNumber($number, $decimals);
    }
    
    public function formatCurrency($amount) {
        return $this->numberFormatter->formatCurrency($amount);
    }
    
    public function getMonthSelector($selectedMonth = null) {
        $months = $this->dateFormatter->getMonthNames();
        $html = '<select name="month">';
        
        foreach ($months as $value => $name) {
            $selected = ($selectedMonth == $value) ? ' selected' : '';
            $html .= "<option value=\"$value\"$selected>$name</option>";
        }
        
        $html .= '</select>';
        return $html;
    }
    
    public function generateReport($data) {
        $report = [];
        $report['title'] = $this->getLocalizedString('report_title');
        $report['generated_at'] = $this->formatDate();
        $report['locale'] = $this->currentLocale;
        
        // データの統計
        if (!empty($data)) {
            $total = array_sum($data);
            $average = $total / count($data);
            $min = min($data);
            $max = max($data);
            
            $report['statistics'] = [
                'total' => $this->formatNumber($total),
                'average' => $this->formatNumber($average, 2),
                'min' => $this->formatNumber($min),
                'max' => $this->formatNumber($max),
                'count' => count($data)
            ];
        }
        
        return $report;
    }
    
    private function getLocalizedString($key) {
        $strings = [
            'ja' => [
                'report_title' => 'レポート',
                'total' => '合計',
                'average' => '平均',
                'minimum' => '最小値',
                'maximum' => '最大値'
            ],
            'en' => [
                'report_title' => 'Report',
                'total' => 'Total',
                'average' => 'Average',
                'minimum' => 'Minimum',
                'maximum' => 'Maximum'
            ],
            'de' => [
                'report_title' => 'Bericht',
                'total' => 'Gesamt',
                'average' => 'Durchschnitt',
                'minimum' => 'Minimum',
                'maximum' => 'Maximum'
            ],
            'fr' => [
                'report_title' => 'Rapport',
                'total' => 'Total',
                'average' => 'Moyenne',
                'minimum' => 'Minimum',
                'maximum' => 'Maximum'
            ]
        ];
        
        return $strings[$this->currentLocale][$key] ?? $key;
    }
    
    public function displaySystemInfo() {
        echo "=== システム情報 ===\n";
        echo "現在の言語: " . $this->currentLocale . "\n";
        echo "ロケール: " . $this->supportedLocales[$this->currentLocale] . "\n";
        echo "文字セット: " . nl_langinfo(CODESET) . "\n";
        echo "小数点記号: '" . nl_langinfo(RADIXCHAR) . "'\n";
        echo "千の位区切り: '" . nl_langinfo(THOUSEP) . "'\n";
        echo "通貨情報: " . nl_langinfo(CRNCYSTR) . "\n";
        echo "現在日時: " . $this->formatDate() . "\n";
    }
}

// 使用例
echo "=== ロケール対応Webアプリケーション ===\n";

$app = new LocaleAwareApplication();

// 利用可能な言語を表示
echo "利用可能な言語: " . implode(', ', $app->getAvailableLanguages()) . "\n\n";

// 各言語でのデモ
foreach ($app->getAvailableLanguages() as $lang) {
    try {
        echo "--- 言語: $lang ---\n";
        $app->setLanguage($lang);
        $app->displaySystemInfo();
        
        // レポート生成
        $sampleData = [1234.56, 2345.67, 3456.78, 4567.89, 5678.90];
        $report = $app->generateReport($sampleData);
        
        echo "\n" . $report['title'] . "\n";
        echo "生成日時: " . $report['generated_at'] . "\n";
        echo "合計: " . $report['statistics']['total'] . "\n";
        echo "平均: " . $report['statistics']['average'] . "\n";
        
        // 月選択のHTML
        echo "\n月選択HTML:\n";
        echo htmlspecialchars($app->getMonthSelector(3)) . "\n";
        
        echo "\n" . str_repeat('-', 40) . "\n\n";
        
    } catch (Exception $e) {
        echo "エラー [$lang]: " . $e->getMessage() . "\n\n";
    }
}
?>

システム情報とデバッグ

1. ロケールサポート確認ツール

<?php
class LocaleDebugger {
    public static function getSystemInfo() {
        return [
            'php_version' => PHP_VERSION,
            'os' => PHP_OS,
            'nl_langinfo_available' => function_exists('nl_langinfo'),
            'setlocale_available' => function_exists('setlocale'),
            'current_locale' => setlocale(LC_ALL, 0),
            'available_locales' => self::getAvailableLocales()
        ];
    }
    
    public static function getAvailableLocales() {
        $locales = [];
        
        // よく使われるロケールをテスト
        $testLocales = [
            'C' => 'C',
            'POSIX' => 'POSIX',
            'ja_JP.UTF-8' => '日本語',
            'en_US.UTF-8' => '英語(アメリカ)',
            'en_GB.UTF-8' => '英語(イギリス)',
            'de_DE.UTF-8' => 'ドイツ語',
            'fr_FR.UTF-8' => 'フランス語',
            'es_ES.UTF-8' => 'スペイン語',
            'it_IT.UTF-8' => 'イタリア語',
            'zh_CN.UTF-8' => '中国語(簡体字)',
            'ko_KR.UTF-8' => '韓国語'
        ];
        
        $originalLocale = setlocale(LC_ALL, 0);
        
        foreach ($testLocales as $locale => $name) {
            if (setlocale(LC_ALL, $locale)) {
                $locales[$locale] = [
                    'name' => $name,
                    'available' => true,
                    'charset' => nl_langinfo(CODESET)
                ];
            } else {
                $locales[$locale] = [
                    'name' => $name,
                    'available' => false,
                    'charset' => null
                ];
            }
        }
        
        // 元のロケールを復元
        setlocale(LC_ALL, $originalLocale);
        
        return $locales;
    }
    
    public static function testAllConstants() {
        $constants = [
            // 時間・日付
            'D_T_FMT' => D_T_FMT,
            'D_FMT' => D_FMT,
            'T_FMT' => T_FMT,
            'T_FMT_AMPM' => T_FMT_AMPM,
            'AM_STR' => AM_STR,
            'PM_STR' => PM_STR,
            
            // 月名
            'MON_1' => MON_1, 'MON_2' => MON_2, 'MON_3' => MON_3,
            'ABMON_1' => ABMON_1, 'ABMON_2' => ABMON_2, 'ABMON_3' => ABMON_3,
            
            // 曜日名
            'DAY_1' => DAY_1, 'DAY_2' => DAY_2, 'DAY_3' => DAY_3,
            'ABDAY_1' => ABDAY_1, 'ABDAY_2' => ABDAY_2, 'ABDAY_3' => ABDAY_3,
            
            // 数値・通貨
            'RADIXCHAR' => RADIXCHAR,
            'THOUSEP' => THOUSEP,
            'CRNCYSTR' => CRNCYSTR,
            
            // 文字セット
            'CODESET' => CODESET
        ];
        
        $results = [];
        
        foreach ($constants as $name => $value) {
            try {
                $result = nl_langinfo($value);
                $results[$name] = [
                    'constant' => $name,
                    'value' => $value,
                    'result' => $result,
                    'success' => true
                ];
            } catch (Exception $e) {
                $results[$name] = [
                    'constant' => $name,
                    'value' => $value,
                    'result' => null,
                    'success' => false,
                    'error' => $e->getMessage()
                ];
            }
        }
        
        return $results;
    }
    
    public static function generateReport() {
        echo "=== nl_langinfo デバッグレポート ===\n\n";
        
        // システム情報
        $sysInfo = self::getSystemInfo();
        echo "システム情報:\n";
        echo "  PHP Version: {$sysInfo['php_version']}\n";
        echo "  OS: {$sysInfo['os']}\n";
        echo "  nl_langinfo利用可能: " . ($sysInfo['nl_langinfo_available'] ? 'はい' : 'いいえ') . "\n";
        echo "  setlocale利用可能: " . ($sysInfo['setlocale_available'] ? 'はい' : 'いいえ') . "\n";
        echo "  現在のロケール: {$sysInfo['current_locale']}\n\n";
        
        // 利用可能ロケール
        echo "利用可能なロケール:\n";
        foreach ($sysInfo['available_locales'] as $locale => $info) {
            $status = $info['available'] ? '✓' : '✗';
            $charset = $info['charset'] ? " [{$info['charset']}]" : '';
            echo "  $status $locale ({$info['name']})$charset\n";
        }
        echo "\n";
        
        // 定数テスト(日本語ロケール)
        if (setlocale(LC_ALL, 'ja_JP.UTF-8')) {
            echo "日本語ロケールでの定数テスト:\n";
            $constantTests = self::testAllConstants();
            
            foreach ($constantTests as $test) {
                if ($test['success']) {
                    echo "  ✓ {$test['constant']}: '{$test['result']}'\n";
                } else {
                    echo "  ✗ {$test['constant']}: エラー\n";
                }
            }
        } else {
            echo "警告: 日本語ロケールが利用できません\n";
        }
    }
}

// デバッグレポートの生成
LocaleDebugger::generateReport();
?>

2. ロケール比較ツール

<?php
class LocaleComparator {
    public static function compareLocales($locales, $items = null) {
        if ($items === null) {
            $items = [
                'CODESET' => CODESET,
                'D_FMT' => D_FMT,
                'T_FMT' => T_FMT,
                'RADIXCHAR' => RADIXCHAR,
                'THOUSEP' => THOUSEP,
                'CRNCYSTR' => CRNCYSTR,
                'MON_1' => MON_1,
                'ABMON_1' => ABMON_1,
                'DAY_1' => DAY_1,
                'ABDAY_1' => ABDAY_1,
                'AM_STR' => AM_STR,
                'PM_STR' => PM_STR
            ];
        }
        
        $originalLocale = setlocale(LC_ALL, 0);
        $comparison = [];
        
        foreach ($locales as $locale) {
            $comparison[$locale] = [];
            
            if (setlocale(LC_ALL, $locale)) {
                foreach ($items as $name => $constant) {
                    $comparison[$locale][$name] = nl_langinfo($constant);
                }
            } else {
                $comparison[$locale] = ['error' => 'ロケール設定失敗'];
            }
        }
        
        // 元のロケールを復元
        setlocale(LC_ALL, $originalLocale);
        
        return $comparison;
    }
    
    public static function displayComparison($comparison) {
        $items = array_keys(reset($comparison));
        
        echo "ロケール比較表:\n";
        echo str_repeat('=', 80) . "\n";
        
        // ヘッダー
        printf("%-15s", "項目");
        foreach (array_keys($comparison) as $locale) {
            printf("%-20s", $locale);
        }
        echo "\n" . str_repeat('-', 80) . "\n";
        
        // データ行
        foreach ($items as $item) {
            printf("%-15s", $item);
            
            foreach ($comparison as $locale => $values) {
                if (isset($values['error'])) {
                    printf("%-20s", "エラー");
                } else {
                    $value = $values[$item] ?? 'N/A';
                    // 長い値は切り詰める
                    if (mb_strlen($value) > 18) {
                        $value = mb_substr($value, 0, 15) . '...';
                    }
                    printf("%-20s", "'" . $value . "'");
                }
            }
            echo "\n";
        }
    }
    
    public static function findDifferences($comparison) {
        $differences = [];
        $items = array_keys(reset($comparison));
        
        foreach ($items as $item) {
            $values = [];
            foreach ($comparison as $locale => $data) {
                if (!isset($data['error'])) {
                    $values[$locale] = $data[$item] ?? null;
                }
            }
            
            // 値が異なるかチェック
            $uniqueValues = array_unique(array_values($values));
            if (count($uniqueValues) > 1) {
                $differences[$item] = $values;
            }
        }
        
        return $differences;
    }
}

// 比較実行例
echo "\n=== ロケール比較 ===\n";

$locales = ['ja_JP.UTF-8', 'en_US.UTF-8', 'de_DE.UTF-8', 'fr_FR.UTF-8'];
$comparison = LocaleComparator::compareLocales($locales);

LocaleComparator::displayComparison($comparison);

echo "\n=== 差異のある項目 ===\n";
$differences = LocaleComparator::findDifferences($comparison);

foreach ($differences as $item => $values) {
    echo "$item:\n";
    foreach ($values as $locale => $value) {
        echo "  $locale: '$value'\n";
    }
    echo "\n";
}
?>

3. パフォーマンステストとベストプラクティス

<?php
class LocalePerformanceTest {
    public static function benchmarkSetlocale($locales, $iterations = 1000) {
        $results = [];
        
        foreach ($locales as $locale) {
            $start = microtime(true);
            
            for ($i = 0; $i < $iterations; $i++) {
                setlocale(LC_ALL, $locale);
            }
            
            $results[$locale] = microtime(true) - $start;
        }
        
        return $results;
    }
    
    public static function benchmarkNlLanginfo($constants, $iterations = 10000) {
        $results = [];
        
        foreach ($constants as $name => $constant) {
            $start = microtime(true);
            
            for ($i = 0; $i < $iterations; $i++) {
                nl_langinfo($constant);
            }
            
            $results[$name] = microtime(true) - $start;
        }
        
        return $results;
    }
    
    public static function testCaching() {
        $locales = ['ja_JP.UTF-8', 'en_US.UTF-8', 'de_DE.UTF-8'];
        $constants = [
            'D_FMT' => D_FMT,
            'T_FMT' => T_FMT,
            'RADIXCHAR' => RADIXCHAR,
            'THOUSEP' => THOUSEP
        ];
        
        $iterations = 1000;
        
        // キャッシュなしのテスト
        $start = microtime(true);
        for ($i = 0; $i < $iterations; $i++) {
            foreach ($locales as $locale) {
                setlocale(LC_ALL, $locale);
                foreach ($constants as $constant) {
                    nl_langinfo($constant);
                }
            }
        }
        $timeWithoutCache = microtime(true) - $start;
        
        // キャッシュありのテスト
        $cache = [];
        $start = microtime(true);
        
        for ($i = 0; $i < $iterations; $i++) {
            foreach ($locales as $locale) {
                foreach ($constants as $name => $constant) {
                    $key = $locale . '_' . $name;
                    
                    if (!isset($cache[$key])) {
                        setlocale(LC_ALL, $locale);
                        $cache[$key] = nl_langinfo($constant);
                    }
                    
                    $value = $cache[$key]; // 使用
                }
            }
        }
        $timeWithCache = microtime(true) - $start;
        
        return [
            'without_cache' => $timeWithoutCache,
            'with_cache' => $timeWithCache,
            'improvement' => $timeWithoutCache / $timeWithCache
        ];
    }
}

// パフォーマンステスト実行
echo "\n=== パフォーマンステスト ===\n";

// setlocaleのベンチマーク
echo "setlocale性能テスト (1000回):\n";
$locales = ['ja_JP.UTF-8', 'en_US.UTF-8', 'de_DE.UTF-8'];
$setlocaleResults = LocalePerformanceTest::benchmarkSetlocale($locales);

foreach ($setlocaleResults as $locale => $time) {
    echo sprintf("  %-15s: %8.4f秒\n", $locale, $time);
}

// nl_langinfoのベンチマーク
echo "\nnl_langinfo性能テスト (10000回):\n";
$constants = [
    'D_FMT' => D_FMT,
    'RADIXCHAR' => RADIXCHAR,
    'CODESET' => CODESET
];
$nlResults = LocalePerformanceTest::benchmarkNlLanginfo($constants);

foreach ($nlResults as $name => $time) {
    echo sprintf("  %-15s: %8.4f秒\n", $name, $time);
}

// キャッシュ効果のテスト
echo "\nキャッシュ効果テスト:\n";
$cacheResults = LocalePerformanceTest::testCaching();

echo sprintf("キャッシュなし: %8.4f秒\n", $cacheResults['without_cache']);
echo sprintf("キャッシュあり: %8.4f秒\n", $cacheResults['with_cache']);
echo sprintf("性能向上:     %8.2f倍\n", $cacheResults['improvement']);
?>

エラーハンドリングとトラブルシューティング

よくある問題と解決法

<?php
class LocaleTroubleshooter {
    public static function diagnoseCommonIssues() {
        $issues = [];
        
        // nl_langinfo関数の存在確認
        if (!function_exists('nl_langinfo')) {
            $issues[] = [
                'type' => 'critical',
                'message' => 'nl_langinfo関数が利用できません',
                'solution' => 'PHPのコンパイル時に--enable-nlsが必要です'
            ];
        }
        
        // setlocale関数の存在確認
        if (!function_exists('setlocale')) {
            $issues[] = [
                'type' => 'critical',
                'message' => 'setlocale関数が利用できません',
                'solution' => 'システムのロケールサポートを確認してください'
            ];
        }
        
        // 基本ロケールの確認
        $originalLocale = setlocale(LC_ALL, 0);
        $basicLocales = ['C', 'POSIX'];
        $availableBasicLocales = [];
        
        foreach ($basicLocales as $locale) {
            if (setlocale(LC_ALL, $locale)) {
                $availableBasicLocales[] = $locale;
            }
        }
        
        if (empty($availableBasicLocales)) {
            $issues[] = [
                'type' => 'warning',
                'message' => '基本的なロケール(C、POSIX)が利用できません',
                'solution' => 'システムのロケール設定を確認してください'
            ];
        }
        
        // UTF-8ロケールの確認
        $utf8Locales = ['ja_JP.UTF-8', 'en_US.UTF-8'];
        $availableUtf8Locales = [];
        
        foreach ($utf8Locales as $locale) {
            if (setlocale(LC_ALL, $locale)) {
                $availableUtf8Locales[] = $locale;
            }
        }
        
        if (empty($availableUtf8Locales)) {
            $issues[] = [
                'type' => 'warning',
                'message' => 'UTF-8ロケールが利用できません',
                'solution' => 'システムにUTF-8ロケールをインストールしてください'
            ];
        }
        
        // 元のロケールを復元
        setlocale(LC_ALL, $originalLocale);
        
        return $issues;
    }
    
    public static function testLocaleSettings($locale) {
        $results = [
            'locale' => $locale,
            'set_success' => false,
            'categories' => [],
            'constants' => [],
            'errors' => []
        ];
        
        $originalLocale = setlocale(LC_ALL, 0);
        
        // ロケール設定のテスト
        if (setlocale(LC_ALL, $locale)) {
            $results['set_success'] = true;
            
            // カテゴリ別の設定確認
            $categories = [
                'LC_ALL' => LC_ALL,
                'LC_CTYPE' => LC_CTYPE,
                'LC_NUMERIC' => LC_NUMERIC,
                'LC_TIME' => LC_TIME,
                'LC_COLLATE' => LC_COLLATE,
                'LC_MONETARY' => LC_MONETARY,
                'LC_MESSAGES' => defined('LC_MESSAGES') ? LC_MESSAGES : null
            ];
            
            foreach ($categories as $name => $constant) {
                if ($constant !== null) {
                    $results['categories'][$name] = setlocale($constant, 0);
                }
            }
            
            // 重要な定数のテスト
            $testConstants = [
                'CODESET' => CODESET,
                'D_FMT' => D_FMT,
                'T_FMT' => T_FMT,
                'RADIXCHAR' => RADIXCHAR,
                'THOUSEP' => THOUSEP
            ];
            
            foreach ($testConstants as $name => $constant) {
                try {
                    $value = nl_langinfo($constant);
                    $results['constants'][$name] = $value;
                } catch (Exception $e) {
                    $results['errors'][$name] = $e->getMessage();
                }
            }
        } else {
            $results['errors']['setlocale'] = "ロケール '$locale' の設定に失敗";
        }
        
        // 元のロケールを復元
        setlocale(LC_ALL, $originalLocale);
        
        return $results;
    }
    
    public static function generateFixSuggestions($issues) {
        $suggestions = [];
        
        foreach ($issues as $issue) {
            switch ($issue['type']) {
                case 'critical':
                    $suggestions[] = "🚨 重要: {$issue['message']}";
                    $suggestions[] = "   解決策: {$issue['solution']}";
                    break;
                    
                case 'warning':
                    $suggestions[] = "⚠️  警告: {$issue['message']}";
                    $suggestions[] = "   解決策: {$issue['solution']}";
                    break;
                    
                case 'info':
                    $suggestions[] = "ℹ️  情報: {$issue['message']}";
                    if (isset($issue['solution'])) {
                        $suggestions[] = "   推奨: {$issue['solution']}";
                    }
                    break;
            }
            $suggestions[] = "";
        }
        
        return $suggestions;
    }
    
    public static function runFullDiagnostic() {
        echo "=== フル診断実行 ===\n\n";
        
        // 一般的な問題の診断
        $issues = self::diagnoseCommonIssues();
        
        if (!empty($issues)) {
            echo "発見された問題:\n";
            $suggestions = self::generateFixSuggestions($issues);
            foreach ($suggestions as $suggestion) {
                echo $suggestion . "\n";
            }
        } else {
            echo "✅ 基本的な問題は見つかりませんでした\n\n";
        }
        
        // 具体的なロケールのテスト
        $testLocales = ['ja_JP.UTF-8', 'en_US.UTF-8', 'C'];
        
        echo "個別ロケールテスト:\n";
        echo str_repeat('-', 50) . "\n";
        
        foreach ($testLocales as $locale) {
            echo "テスト中: $locale\n";
            $results = self::testLocaleSettings($locale);
            
            if ($results['set_success']) {
                echo "  ✅ ロケール設定成功\n";
                echo "  文字セット: " . ($results['constants']['CODESET'] ?? 'N/A') . "\n";
                echo "  日付形式: " . ($results['constants']['D_FMT'] ?? 'N/A') . "\n";
                echo "  小数点: '" . ($results['constants']['RADIXCHAR'] ?? 'N/A') . "'\n";
            } else {
                echo "  ❌ ロケール設定失敗\n";
                if (!empty($results['errors'])) {
                    foreach ($results['errors'] as $error) {
                        echo "     エラー: $error\n";
                    }
                }
            }
            echo "\n";
        }
    }
}

// フル診断の実行
LocaleTroubleshooter::runFullDiagnostic();
?>

まとめ

nl_langinfo関数は、国際化対応のWebアプリケーション開発において非常に重要な役割を果たします。この関数を適切に使用することで、ユーザーの地域設定に応じた自然な表示を実現できます。

重要なポイント

  • ロケール依存: 事前にsetlocale()でロケールを設定する必要がある
  • 豊富な情報: 日付形式、数値形式、通貨記号、文字セットなど多様な情報を取得可能
  • 動的対応: ユーザーの設定に応じてリアルタイムで表示形式を変更可能
  • システム依存: 利用可能なロケールはシステムの設定に依存

ベストプラクティス

  1. エラーハンドリング: ロケール設定の失敗に備えた適切な例外処理
  2. キャッシュ活用: 頻繁に使用する情報はキャッシュして性能を向上
  3. フォールバック: 希望するロケールが利用できない場合の代替手段を準備
  4. テスト: 様々なロケール環境での動作確認を実施
  5. ドキュメント化: サポートするロケールと制限事項を明記

推奨される使用場面

  1. 多言語ECサイト: 通貨表示、日付表示の地域対応
  2. 国際的なWebアプリ: ユーザーインターフェースのローカライズ
  3. レポートシステム: 地域に応じた数値・日付フォーマット
  4. コンテンツ管理システム: 多言語コンテンツの適切な表示

nl_langinfo関数を理解し適切に使用することで、真にグローバルなWebアプリケーションを開発できます。特に多様なユーザーベースを持つ現代のWebサービスでは、この知識は必須と言えるでしょう。

タイトルとURLをコピーしました