PHP多语言方案怎么设计

wen PHP项目 5

本文目录导读:

PHP多语言方案怎么设计

  1. 基础架构设计
  2. 核心翻译类实现
  3. 语言文件示例
  4. 复数翻译示例
  5. 辅助函数和全局集成
  6. 高级功能
  7. 配置文件
  8. 最佳实践建议

我来设计一个PHP多语言方案,涵盖从简单到复杂的各种场景。

基础架构设计

目录结构

project/
├── app/
│   ├── Languages/
│   │   ├── en/
│   │   │   ├── messages.php
│   │   │   ├── validation.php
│   │   │   └── errors.php
│   │   ├── zh-CN/
│   │   │   ├── messages.php
│   │   │   └── ...
│   │   └── ja/
│   │       └── ...
│   ├── Core/
│   │   └── Translator.php
│   └── Helpers/
│       └── translate_helper.php
└── config/
    └── language.php

核心翻译类实现

<?php
// app/Core/Translator.php
namespace App\Core;
class Translator
{
    private static $instance = null;
    private $translations = [];
    private $locale = 'en';
    private $fallbackLocale = 'en';
    private $loadedFiles = [];
    // 语言包目录
    private $langPath = null;
    // 支持的语言列表
    private $supportedLocales = ['en', 'zh-CN', 'ja', 'fr', 'de'];
    // 缓存
    private $cache = [];
    private function __construct()
    {
        $this->langPath = dirname(__DIR__) . '/Languages/';
        $this->setLocale($this->getBrowserLocale());
    }
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    /**
     * 设置当前语言
     */
    public function setLocale(string $locale): void
    {
        $locale = $this->normalizeLocale($locale);
        if (in_array($locale, $this->supportedLocales)) {
            $this->locale = $locale;
            $this->loadLocale($locale);
        }
        // 设置PHP locale
        setlocale(LC_ALL, $locale . '.UTF-8');
        // 设置时区
        date_default_timezone_set($this->getTimezoneForLocale($locale));
    }
    /**
     * 获取当前语言
     */
    public function getLocale(): string
    {
        return $this->locale;
    }
    /**
     * 翻译方法
     */
    public function trans(string $key, array $replacements = [], string $locale = null): string
    {
        $locale = $locale ?: $this->locale;
        // 检查缓存
        $cacheKey = md5($key . $locale);
        if (isset($this->cache[$cacheKey])) {
            return $this->applyReplacements($this->cache[$cacheKey], $replacements);
        }
        // 解析key:file.key.nested
        $segments = explode('.', $key);
        $file = array_shift($segments);
        // 加载语言文件
        if (!isset($this->translations[$locale][$file])) {
            $this->loadFile($locale, $file);
        }
        // 查找翻译
        $translation = $this->findTranslation($this->translations[$locale][$file] ?? [], $segments);
        // 如果没找到,尝试fallback
        if ($translation === null && $locale !== $this->fallbackLocale) {
            $this->loadLocale($this->fallbackLocale);
            $translation = $this->findTranslation($this->translations[$this->fallbackLocale][$file] ?? [], $segments);
        }
        // 最终fallback返回key
        $translation = $translation ?? $key;
        // 缓存结果
        $this->cache[$cacheKey] = $translation;
        return $this->applyReplacements($translation, $replacements);
    }
    /**
     * 复数翻译
     */
    public function transChoice(string $key, int $number, array $replacements = []): string
    {
        $locale = $this->locale;
        $replacements['count'] = $number;
        $segments = explode('.', $key);
        $file = array_shift($segments);
        $this->loadFile($locale, $file);
        $translations = $this->findTranslation($this->translations[$locale][$file] ?? [], $segments);
        if (is_array($translations)) {
            $translation = $this->selectPluralForm($translations, $number, $locale);
        } else {
            $translation = $translations ?? $key;
        }
        return $this->applyReplacements($translation, $replacements);
    }
    /**
     * 加载语言文件
     */
    private function loadFiles(string $locale): void
    {
        $path = $this->langPath . $locale . '/';
        if (!is_dir($path)) {
            return;
        }
        $files = glob($path . '*.php');
        foreach ($files as $file) {
            $filename = basename($file, '.php');
            $this->loadFile($locale, $filename);
        }
    }
    /**
     * 加载单个文件
     */
    private function loadFile(string $locale, string $file): void
    {
        if (!isset($this->loadedFiles[$locale][$file])) {
            $path = $this->langPath . $locale . '/' . $file . '.php';
            if (file_exists($path)) {
                $this->translations[$locale][$file] = require $path;
                $this->loadedFiles[$locale][$file] = true;
            }
        }
    }
    /**
     * 查找嵌套key
     */
    private function findTranslation($translations, array $segments)
    {
        $result = $translations;
        foreach ($segments as $segment) {
            if (!isset($result[$segment])) {
                return null;
            }
            $result = $result[$segment];
        }
        return $result;
    }
    /**
     * 应用替换
     */
    private function applyReplacements(string $translation, array $replacements): string
    {
        foreach ($replacements as $key => $value) {
            $translation = str_replace(':' . $key, $value, $translation);
        }
        return $translation;
    }
    /**
     * 选择复数形式
     */
    private function selectPluralForm(array $forms, int $number, string $locale): string
    {
        // 中文没有复数形式
        if (in_array($locale, ['zh-CN', 'ja', 'ko'])) {
            return $forms['other'] ?? reset($forms);
        }
        // 英文复数规则
        if (in_array($locale, ['en', 'de', 'fr'])) {
            return $number == 1 ? $forms['one'] : $forms['other'];
        }
        // 俄语等复杂复数
        if ($locale === 'ru') {
            $mod10 = $number % 10;
            $mod100 = $number % 100;
            if ($mod10 == 1 && $mod100 != 11) {
                return $forms['one'];
            }
            if ($mod10 >= 2 && $mod10 <= 4 && ($mod100 < 10 || $mod100 >= 20)) {
                return $forms['few'];
            }
            return $forms['many'];
        }
        // 默认
        return $forms['other'] ?? reset($forms);
    }
    /**
     * 获取浏览器语言
     */
    private function getBrowserLocale(): string
    {
        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
            $browserLocales = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
            $locale = substr($browserLocales[0], 0, 2);
            // 映射常见浏览器语言
            $mapping = [
                'en' => 'en',
                'zh' => 'zh-CN',
                'ja' => 'ja',
                'fr' => 'fr',
            ];
            return $mapping[$locale] ?? 'en';
        }
        return 'en';
    }
    /**
     * 规范化语言代码
     */
    private function normalizeLocale(string $locale): string
    {
        // 统一格式:zh-CN, en-GB
        $localeMap = [
            'zh_cn' => 'zh-CN',
            'zh-tw' => 'zh-TW',
            'en_us' => 'en',
            'en_gb' => 'en',
        ];
        return $localeMap[strtolower($locale)] ?? $locale;
    }
    /**
     * 获取时区
     */
    private function getTimezoneForLocale(string $locale): string
    {
        $timezones = [
            'en' => 'America/New_York',
            'zh-CN' => 'Asia/Shanghai',
            'ja' => 'Asia/Tokyo',
            'fr' => 'Europe/Paris',
        ];
        return $timezones[$locale] ?? 'UTC';
    }
    /**
     * 创建翻译辅助函数
     */
    public function t(string $key, array $replacements = []): string
    {
        return $this->trans($key, $replacements);
    }
}

语言文件示例

<?php
// app/Languages/zh-CN/messages.php
return [
    'welcome' => '欢迎来到 :app_name',
    'login' => '登录',
    'logout' => '退出登录',
    'register' => '注册',
    'navigation' => [
        'home' => '首页',
        'about' => '关于我们',
        'contact' => '联系我们',
        'products' => '产品',
    ],
    'messages' => [
        'success' => '操作成功',
        'error' => '操作失败',
        'warning' => '警告',
        'info' => '提示信息',
    ],
    'greetings' => [
        'morning' => '早上好',
        'afternoon' => '下午好',
        'evening' => '晚上好',
    ],
];
// app/Languages/en/messages.php
return [
    'welcome' => 'Welcome to :app_name',
    'login' => 'Login',
    'logout' => 'Logout',
    'register' => 'Register',
    // ... 其他英文翻译
];

复数翻译示例

<?php
// app/Languages/en/messages.php
return [
    'items_count' => [
        'one' => ':count item',
        'other' => ':count items',
    ],
];
// app/Languages/zh-CN/messages.php
return [
    'items_count' => [
        'other' => '共 :count 个项目',
    ],
];

辅助函数和全局集成

<?php
// app/Helpers/translate_helper.php
if (!function_exists('t')) {
    function t(string $key, array $replacements = []): string
    {
        return \App\Core\Translator::getInstance()->trans($key, $replacements);
    }
}
if (!function_exists('trans')) {
    function trans(string $key, array $replacements = []): string
    {
        return t($key, $replacements);
    }
}
// 简单调用
echo t('welcome', ['app_name' => 'MyApp']);
echo t('navigation.home');
echo trans('messages.success');
// 复数
echo $translator->transChoice('items_count', 5); // "5 items"
echo $translator->transChoice('items_count', 1); // "1 item"
// 切换语言
$translator = \App\Core\Translator::getInstance();
$translator->setLocale('zh-CN');
echo $translator->trans('welcome', ['app_name' => '我的应用']);

高级功能

1 语言检测中间件

<?php
// app/Middleware/LocaleMiddleware.php
class LocaleMiddleware
{
    public function handle($request, $next)
    {
        $translator = \App\Core\Translator::getInstance();
        // 优先级:URL参数 > Session > Cookie > 浏览器设置
        $locale = $request->get('lang') 
            ?? $_SESSION['locale'] 
            ?? $_COOKIE['locale'] 
            ?? null;
        if ($locale) {
            $translator->setLocale($locale);
            $_SESSION['locale'] = $translator->getLocale();
        }
        // 保存到Cookie
        setcookie('locale', $translator->getLocale(), time() + 3600 * 24 * 30);
        return $next($request);
    }
}

2 SEO友好URL

<?php
// URL格式: /en/products, /zh-CN/products
class LocaleRouter
{
    public function generateUrl(string $path, array $params = [], string $locale = null): string
    {
        $locale = $locale ?: \App\Core\Translator::getInstance()->getLocale();
        $baseUrl = "/$locale/$path";
        if (!empty($params)) {
            $baseUrl .= '?' . http_build_query($params);
        }
        return $baseUrl;
    }
}

3 数据库翻译

<?php
// app/Models/Product.php
class Product extends Model
{
    // 可翻译字段
    protected $translatable = ['name', 'description'];
    public function getAttribute($key)
    {
        $locale = \App\Core\Translator::getInstance()->getLocale();
        if (in_array($key, $this->translatable)) {
            $key = $key . '_' . $locale;
        }
        return parent::getAttribute($key);
    }
}
// 数据库表结构
// products table
// id, name_en, name_zh-CN, name_ja, description_en, ...

配置文件

<?php
// config/language.php
return [
    // 默认语言
    'default_locale' => 'en',
    // 后备语言
    'fallback_locale' => 'en',
    // 支持的语言
    'supported_locales' => [
        'en' => 'English',
        'zh-CN' => '简体中文',
        'ja' => '日本語',
        'fr' => 'Français',
    ],
    // 语言切换时刷新页面
    'redirect_on_change' => true,
    // 缓存设置
    'cache' => [
        'enabled' => true,
        'ttl' => 3600, // 1小时
    ],
    // 复数规则
    'plural_rules' => [
        // 每种语言对应的复数规则
    ],
];

最佳实践建议

  1. 性能优化
    • 使用APCu或Redis缓存翻译数组
    • 按需只加载特定的语言文件
    • 使用PHP opcache
// 缓存优化示例
public function loadFile($locale, $file)
{
    $cacheKey = "lang.$locale.$file";
    if ($this->cacheEnabled && apcu_exists($cacheKey)) {
        return apcu_fetch($cacheKey);
    }
    // 加载文件...
    if ($this->cacheEnabled) {
        apcu_store($cacheKey, $translations, $this->cacheTtl);
    }
}
  1. 开发工具

    • 开发翻译代码生成器
    • 自动化提取需要翻译的字符串
    • 创建翻译检查器
  2. 测试

    class TranslationTest extends PHPUnit\Framework\TestCase
    {
     public function testTranslationsExist()
     {
         $translator = Translator::getInstance();
         foreach ($this->getSupportedLocales() as $locale) {
             $translator->setLocale($locale);
             // 检查所有key都存在
             foreach ($this->getAllKeys() as $key) {
                 $this->assertNotEquals(
                     $key, 
                     $translator->trans($key),
                     "Translation missing for key: $key in locale: $locale"
                 );
             }
         }
     }
    }

这个PHP多语言方案涵盖了从基本语言切换、复数翻译、SEO优化到缓存性能的完整实现,适用于各种规模的项目。

抱歉,评论功能暂时关闭!