本文目录导读:

我来设计一个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' => [
// 每种语言对应的复数规则
],
];
最佳实践建议
- 性能优化
- 使用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);
}
}
-
开发工具
- 开发翻译代码生成器
- 自动化提取需要翻译的字符串
- 创建翻译检查器
-
测试
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优化到缓存性能的完整实现,适用于各种规模的项目。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播