ベストプラクティス多言語ウェブサイト


179

私はこの質問にかなりの数か月間苦労してきましたが、私は以前にすべての可能なオプションを検討する必要があった状況にありませんでした。今は、可能性を知り、自分の個人的な好みを作成して、今後のプロジェクトで使用する時がきたと感じています。

最初に私が探している状況をスケッチしましょう

これからずっと使っているコンテンツ管理システムをアップグレード/再開発しようとしています。しかし、私は多言語がこのシステムの大きな改善だと感じています。以前はフレームワークを使用していませんでしたが、次のプロジェクトではLaraval4を使用します。Laravelは、PHPをコーディングするためのよりクリーンな方法の最良の選択のようです。Sidenote: Laraval4 should be no factor in your answer。プラットフォームやフレームワークに依存しない一般的な翻訳方法を探しています。

翻訳すべきもの

私が探しているシステムは、できるだけユーザーフレンドリーである必要があるため、翻訳の管理方法はCMS内にある必要があります。翻訳ファイルやhtml / php解析済みテンプレートを変更するためにFTP接続を開始する必要はないはずです。

さらに、おそらく追加のテーブルを作成する必要なしに、複数のデータベーステーブルを変換する最も簡単な方法を探しています。

何を思いついたの

私はすでに自分で物事を探して、読んで、試しています。私にはいくつかのオプションがあります。しかし、私はまだ自分が本当に求めているもののベストプラクティスの方法に到達したようには感じていません。現在、これは私が思いついたものですが、この方法には副作用もあります。

  1. PHP解析済みテンプレート:テンプレートシステムはPHPで解析する必要があります。このようにして、テンプレートを開いて変更することなく、翻訳されたパラメーターをHTMLに挿入できます。その上、PHPで解析されたテンプレートを使用すると、言語ごとにサブフォルダーを作成する代わりに、Webサイト全体に対して1つのテンプレートを使用できます(以前に使用していた)。このターゲットに到達する方法は、Smarty、TemplatePower、Laravel's Blade、またはその他のテンプレートパーサーのいずれかです。私が言ったように、これは書かれた解決策に依存しないはずです。
  2. データベース駆動:おそらく私はこれについて再度言及する必要はありません。しかし、ソリューションはデータベース主導である必要があります。CMSはオブジェクト指向およびMVCを目的としているため、文字列の論理データ構造について考える必要があります。私のテンプレートは構造化されているので:templates / Controller / View.phpこの構造はおそらく最も理にかなっています:Controller.View.parameter。データベーステーブルには、これらのフィールドとvalueフィールドが含まれます。テンプレート内では、次のような並べ替えメソッドを使用できecho __('Controller.View.welcome', array('name', 'Joshua'))、パラメータにはが含まれますWelcome, :name。したがって、結果は次のとおりWelcome, Joshuaです。:nameなどのパラメーターはエディターが理解しやすいので、これはこれを行うための良い方法のようです。
  3. 低データベース負荷:もちろん、これらの文字列が移動中にロードされている場合、上記のシステムはデータベース負荷の負荷を引き起こします。したがって、管理環境で編集/保存されるとすぐに言語ファイルを再レンダリングするキャッシュシステムが必要になります。ファイルが生成されるため、適切なファイルシステムレイアウトも必要です。私たちは一緒に行くことができますねlanguages/en_EN/Controller/View.phpかの.ini、どんなスーツあなたが最高。おそらく、.iniは、最終的にはさらに速く解析されます。これにはのデータが含まれているはずformat parameter=value; です。レンダリングされる各ビューには、独自の言語ファイルが存在する場合はそれを含めることができるため、これがこれを行うための最良の方法だと思います。次に、言語パラメーターをグローバルスコープではなく特定のビューにロードして、パラメーターが互いに上書きされないようにする必要があります。
  4. データベーステーブルの翻訳:これは実際、私が最も心配していることです。ニュース/ページ/その他の翻訳を作成する方法を探しています。できるだけ早く。モジュールごとに2つのテーブル(たとえばNewsおよびNews_translations)を使用することもできますが、優れたシステムを実現するために多くの作業を行うように感じます。私はに基づいていて思いついたことのひとつdata versioning、私が書いたシステムは:1人のデータベーステーブル名がありTranslations、このテーブルはのユニークな組み合わせを持っているlanguagetablenameprimarykey。たとえば、en_En / News / 1(ID = 1の英語版のニュースアイテムを参照)。ただし、この方法には2つの大きな欠点があります。まず、このテーブルはデータベースに大量のデータがあるとかなり長くなる傾向があります。次に、この設定を使用してテーブルを検索するのは大変です。たとえば、アイテムのSEOスラッグを検索することは全文検索であり、かなり馬鹿げています。しかし、その一方で、すべてのテーブルで翻訳可能なコンテンツを非常に高速に作成する簡単な方法ですが、このプロが短所を強調しすぎているとは思いません。
  5. フロントエンドの作業:また、フロントエンドについても検討する必要があります。もちろん、利用可能な言語をデータベースに保存し、必要な言語を(非)アクティブにします。このように、スクリプトはドロップダウンを生成して言語を選択でき、バックエンドはCMSを使用してどの翻訳を実行できるかを自動的に決定できます。ビューの言語ファイルを取得するとき、またはWebサイトのコンテンツアイテムの適切な翻訳を取得するときに、選択した言語(en_ENなど)が使用されます。

それで、彼らはそこにいます。これまでの私の考え。日付などのローカリゼーションオプションはまだ含まれていませんが、私のサーバーはPHP5.3.2 +をサポートしているため、http://devzone.zend.com/1500/internationalization-inで説明されているように、intl拡張機能を使用することをお勧めします。-php-53 / -しかし、これはその後の開発スタジアムで使用されます。現時点では、主な問題は、Webサイトのコンテンツの翻訳のベストプラクティスをどのように行うかです。

ここで説明したすべてのことに加えて、まだ決めていないことがまだあります。簡単な質問のように見えますが、実際には頭痛の種となっています。

URL変換?これをするべきかどうか?そしてどのように?

つまり、このURLがある場合http://www.domain.com/about-us、英語がデフォルトの言語です。http://www.domain.com/over-ons言語としてオランダ語を選択した場合、このURLを翻訳する必要がありますか?または、簡単な道を進み、に表示されているページのコンテンツを変更するだけ/aboutです。最後のものは、同じURLの複数のバージョンを生成するため、有効なオプションではないように思われます。このコンテンツのインデックス作成は、正しく失敗します。

別のオプションはhttp://www.domain.com/nl/about-us代わりに使用しています。これにより、少なくともコンテンツごとに一意のURLが生成されます。また、別の言語に移動するのも簡単です。たとえばhttp://www.domain.com/en/about-us、提供されるURLは、Googleと人間の両方の訪問者にとって理解しやすくなります。このオプションを使用して、デフォルトの言語をどのように処理しますか?デフォルトの言語で、デフォルトで選択されている言語を削除する必要がありますか?リダイレクトようhttp://www.domain.com/en/about-ushttp://www.domain.com/about-usCMSが一つだけの言語用のセットアップのときURLで、この言語識別を持ってする必要がないので、私の目には...これは、最適なソリューションです。

3番目のオプションは、両方のオプションを組み合わせたものhttp://www.domain.com/about-usです。メイン言語として「言語識別なし」のURL()を使用します。そして、サブ言語用に翻訳されたSEOスラグのURLを使用します:http://www.domain.com/nl/over-onshttp://www.domain.com/de/uber-uns

私の質問であなたの頭が割れるのを願っています。ここで質問としてすでに問題を解決するのに役立ちました。これまでに使用した方法と、今後のCMSのために持っているアイデアを確認する機会を与えてくれました。

このテキストの束を読んでいただきありがとうございます。

// Edit #1

私は言及するのを忘れていました:__()関数は与えられた文字列を翻訳するためのエイリアスです。このメソッド内には、翻訳がまだ利用できない場合にデフォルトのテキストがロードされる、ある種のフォールバックメソッドがあることは明らかです。翻訳がない場合は、挿入するか、翻訳ファイルを再生成する必要があります。


回答:


115

トピックの前提

多言語サイトには3つの異なる側面があります。

  • インターフェース翻訳
  • コンテンツ
  • URLルーティング

これらはすべて異なる方法で相互接続されていますが、CMSの観点からは、異なるUI要素を使用して管理され、異なる方法で保存されます。最初の2つの実装と理解に自信を持っているようです。質問は後者の側面に関するものでした- "URL変換?これを行うべきかどうか?どうやって?"

どのようなURLで構成できますか?

IDNを気にしないでください。代わりに音訳を好む(また、転写とローマ字化)。一見すると、IDNは国際的なURLの実行可能なオプションのように見えますが、実際には次の2つの理由により、宣伝されているとおりに機能しません。

  • 一部のブラウザは、ASCII以外の文字を'ч'またはに'ž'変換'%D1%87'します。'%C5%BE'
  • ユーザーがカスタムテーマを持っている場合、テーマのフォントにはそれらの文字の記号がない可能性が非常に高い

私は実際に数年前にYiiベースのプロジェクト(恐ろしいフレームワーク、IMHO)でIDNアプローチを試みました。私はその解決策をこする前に、上記の問題の両方に遭遇しました。また、攻撃経路の可能性もあると思います。

利用可能なオプション...私がそれらを見ると。

基本的に、次のように抽象化できる2つの選択肢があります。

  • http://site.tld/[:query][:query]言語とコンテンツの選択の両方を決定する場所

  • http://site.tld/[:language]/[:query][:language]URLの一部は言語の選択を定義し[:query]、コンテンツを識別するためにのみ使用されます

クエリはΑおよびΩです。

あなたが選ぶとしましょう http://site.tld/[:query]

その場合、1つの主要な言語ソースがあり[:query]ます。セグメントのコンテンツです。および2つの追加ソース:

  • $_COOKIE['lang']その特定のブラウザの
  • HTTP Accept-Language (1)(2)ヘッダー内の言語のリスト

まず、クエリを定義済みのルーティングパターンの1つと照合する必要があります(Laravelを選択した場合は、こちらをお読みください)。パターンが一致したら、言語を見つける必要があります。

パターンのすべてのセグメントを通過する必要があります。これらのすべてのセグメントの潜在的な翻訳を見つけて、使用された言語を特定します。追加の2つのソース(cookieとヘッダー)は、ルーティングの競合が発生した場合(「if」ではない)に解決するために使用されます。

例を挙げましょうhttp://site.tld/blog/novinka

それはの音訳であり"блог, новинка"、英語でそれはおよそを意味します"blog", "latest"

すでにお気づきのように、ロシア語では「блог」は「ブログ」として音訳されます。つまり、最初の部分[:query]最良のシナリオ)では['en', 'ru']、可能な言語のリストが表示されます。次に、次のセグメント-"novinka"を取ります。それは可能性のリストに1つの言語しか持っていないかもしれません:['ru']

リストに項目が1つあれば、言語は正常に見つかりました。

しかし、場合によっては、2(例:ロシア語とウクライナ語)以上の可能性、..または0の可能性で終わる場合。正しいオプションを見つけるには、cookieやヘッダーを使用する必要があります。

他のすべてが失敗した場合は、サイトのデフォルト言語を選択します。

パラメータとしての言語

別の方法は、次のように定義できるURLを使用することです。 http://site.tld/[:language]/[:query]。この場合、クエリを翻訳するときに、言語を推測する必要はありません。その時点で、どちらを使用するかがわかっているためです。

また、二次的な言語ソースとして、Cookie値があります。しかし、ここでは「コールドスタート」(ユーザーが初めてカスタムクエリでサイトを開いたとき)の場合に不明な量の可能な言語を扱っていないため、Accept-Languageヘッダーをいじる意味はありません。

代わりに、3つの単純な優先オプションがあります。

  1. [:language]セグメントが設定されている場合は、それを使用します
  2. $_COOKIE['lang']設定されている場合は、それを使用します
  3. デフォルトの言語を使用

言語を入手したら、クエリの翻訳を試み、翻訳が失敗した場合は、その特定のセグメントの「デフォルト値」を使用します(ルーティング結果に基づく)。

ここに3番目のオプションはありませんか?

はい、技術的には両方のアプローチを組み合わせることができますが、これはプロセスを複雑にし、URLを手動でに変更し、ニュースページをドイツ語に変更するhttp://site.tld/en/newsことhttp://site.tld/de/newsを期待する人々にのみ対応します。

しかし、このケースでも、Cookieの値(以前の言語の選択に関する情報が含まれる)を使用して軽減できる可能性があり、実装する魔法と希望は少なくなります。

どちらのアプローチを使用しますか?

ご想像のとおり、お勧めします http://site.tld/[:language]/[:query]、より賢明なオプションとしてします。

また、実際の状況では、URLの3番目の主要部分は「タイトル」になります。オンラインショップの商品名やニュースサイトの記事の見出しなど。

例: http://site.tld/en/news/article/121415/EU-as-global-reserve-currency

この場合'/news/article/121415'はクエリであり、'EU-as-global-reserve-currency'タイトルです。純粋にSEOの目的で。

Laravelでそれを行うことはできますか?

ちょっと、デフォルトではありません。

私はそれにあまり慣れていませんが、私が見てきたことから、Laravelは単純なパターンベースのルーティングメカニズムを使用しています。多言語ルーティングでは、さまざまな形式のストレージ(データベース、キャッシュ、構成ファイル)にアクセスする必要があるため、多言語URLを実装するには、おそらくコアクラス拡張する必要があります。

ルーティングされました。今何?

すべての結果として、現在の言語とクエリの翻訳されたセグメントという2つの貴重な情報が得られます。これらの値は、結果を生成するクラスにディスパッチするために使用できます。

基本的に、次のURL:(http://site.tld/ru/blog/novinkaまたはのないバージョン'/ru')は次のようになります

$parameters = [
   'language' => 'ru',
   'classname' => 'blog',
   'method' => 'latest',
];

ディスパッチに使用するもの:

$instance = new {$parameter['classname']};
$instance->{'get'.$parameters['method']}( $parameters );

..または、特定の実装に応じて、そのいくつかのバリエーション。


1
さらに別の洞察をありがとう!とても思慮深い!言語パラメーターもURLに含めることを考えていました。これは単に、ユーザーだけでなくSEOの目的のためにも、特定の言語を識別するための最良の方法のようです。ユーザーが/ en / newsを/ de / newsに変更した場合、私の考えは、たとえば/ de / nachrichtenへの301(永続的な)リダイレクトを行うことでした。各言語のページごとに一意のURLが1つしかないことを確認するために(これもSEOの目的で)
Joshua-Pendo

ベストアンサーの選択はますます難しくなっています。現在、賞金の少なくとも一部に値する約3/4のアンサーがあります。それらを組み合わせると、私が一緒に解決したかったすべてに対する確かな答えになります:)
ジョシュア-ペンド

私はあなたの回答を受け入れ、あなたがURL変換であなたが与えた詳細な回答に対する少なくともいくらかの追加の代表者をあなたに与えるために。高く評価!ただし、賞金は、プラットフォームに依存しない方法で私の質問のすべての側面に答えたので、下の人に授与されます。
ジョシュア-ペンド

52

Thomas Bleyによって提案された、プリプロセッサを使用したパフォーマンスヒットなしのi18nの実装

職場で、私たちは最近、いくつかのプロパティでi18nの実装を行いました。私たちが悩んでいたことの1つは、オンザフライ翻訳の処理のパフォーマンスへの影響でした。それから、Thomas Bleyによるこの素晴らしいブログ投稿を発見しましたこれは、i18nを使用して、パフォーマンスの問題を最小限に抑えながら大きなトラフィック負荷を処理する方法に影響を与えました。

PHPでコストが高いことがわかっているすべての翻訳操作で関数を呼び出す代わりに、基本ファイルをプレースホルダーで定義し、プリプロセッサーを使用してそれらのファイルをキャッシュします(ファイルの変更時間を保存して、常に最新のコンテンツ)。

翻訳タグ

Thomasはタグ{tr}{/tr}タグを使用して、翻訳の開始点と終了点を定義します。TWIGを使用{しているため、混乱を避けるために使用したくないので[%tr%][%/tr%]代わりにを使用します。基本的に、これは次のようになります。

`return [%tr%]formatted_value[%/tr%];`

Thomasは、ファイルでベース英語を使用することを推奨していることに注意してください。英語で値を変更した場合にすべての翻訳ファイルを変更する必要がないため、これは行いません。

INIファイル

次に、各言語のINIファイルを次の形式で作成しますplaceholder = translated

// lang/fr.ini
formatted_value = number_format($value * Model_Exchange::getEurRate(), 2, ',', ' ') . '€'

// lang/en_gb.ini
formatted_value = '£' . number_format($value * Model_Exchange::getStgRate())

// lang/en_us.ini
formatted_value = '$' . number_format($value)

それだけで鍵ペアを取得し、ユーザーがCMSの内部でこれらを変更することを可能にするのは簡単だろうpreg_split\nか、=およびINIファイルに書き込みCMSができ作ります。

プリプロセッサコンポーネント

基本的に、Thomasは、翻訳ファイルを取得してディスク上に静的PHPファイルを作成するために、このようなジャストインタイムの「コンパイラ」(実際には、プリプロセッサです)関数を使用することを推奨しています。このようにして、ファイル内のすべての文字列に対して翻訳関数を呼び出すのではなく、翻訳されたファイルを基本的にキャッシュします。

// This function was written by Thomas Bley, not by me
function translate($file) {
  $cache_file = 'cache/'.LANG.'_'.basename($file).'_'.filemtime($file).'.php';
  // (re)build translation?
  if (!file_exists($cache_file)) {
    $lang_file = 'lang/'.LANG.'.ini';
    $lang_file_php = 'cache/'.LANG.'_'.filemtime($lang_file).'.php';

    // convert .ini file into .php file
    if (!file_exists($lang_file_php)) {
      file_put_contents($lang_file_php, '<?php $strings='.
        var_export(parse_ini_file($lang_file), true).';', LOCK_EX);
    }
    // translate .php into localized .php file
    $tr = function($match) use (&$lang_file_php) {
      static $strings = null;
      if ($strings===null) require($lang_file_php);
      return isset($strings[ $match[1] ]) ? $strings[ $match[1] ] : $match[1];
    };
    // replace all {t}abc{/t} by tr()
    file_put_contents($cache_file, preg_replace_callback(
      '/\[%tr%\](.*?)\[%\/tr%\]/', $tr, file_get_contents($file)), LOCK_EX);
  }
  return $cache_file;
}

注:正規表現が機能することは確認していません。社内サーバーからはコピーしていませんが、操作の仕組みを確認できます。

それを呼び出す方法

繰り返しますが、この例は私からではなく、Thomas Bleyからのものです。

// instead of
require("core/example.php");
echo (new example())->now();

// we write
define('LANG', 'en_us');
require(translate('core/example.php'));
echo (new example())->now();

言語をCookie(またはCookieを取得できない場合はセッション変数)に格納し、リクエストごとに取得します。これをオプションの$_GETパラメーターと組み合わせて言語をオーバーライドすることもできますが、人気のあるページを確認することが難しくなり、インバウンドの価値が低下するため、サブドメインごとの言語またはページごとの言語はお勧めしません。リンクが広がるのを防ぐためです。

なぜこの方法を使用するのですか?

この前処理方法は、次の3つの理由で気に入っています。

  1. まれにしか変化しないコンテンツに対して一連の関数全体を呼び出さないことによる大きなパフォーマンスの向上(このシステムでは、フランス語で10万人の訪問者が翻訳の置換を実行するのは一度だけです)。
  2. シンプルなフラットファイルを使用し、純粋なPHPソリューションであるため、データベースに負荷がかかりません。
  3. 翻訳内でPHP式を使用する機能。

翻訳されたデータベースコンテンツの取得

と呼ばれるデータベースのコンテンツの列を追加するだけで、前に定義languageしたLANG定数にアクセサーメソッドを使用するため、SQL呼び出し(悲しいことにZF1を使用)は次のようになります。

$query = select()->from($this->_name)
                 ->where('language = ?', User::getLang())
                 ->where('id       = ?', $articleId)
                 ->limit(1);

私たちの記事は、以上の複合主キー持っidlanguage記事そうは54すべての言語に存在することができます。私たちのLANGデフォルトen_US指定されていない場合。

URLスラグ変換

ここで2つのことを組み合わせます。1つはブートストラップの関数で、$_GET言語のパラメーターを受け入れてCookie変数をオーバーライドします。もう1つは、複数のスラグを受け入れるルーティングです。次に、ルーティングで次のようなことを行うことができます。

"/wilkommen" => "/welcome/lang/de"
... etc ...

これらは、管理パネルから簡単に書き込むことができるフラットファイルに保存できます。JSONまたはXMLは、それらをサポートするための適切な構造を提供します。

その他のいくつかのオプションに関するメモ

PHPベースのオンザフライ翻訳

これらが前処理済みの翻訳よりも優れていることはわかりません。

フロントエンドベースの翻訳

私は長い間これらを興味深いものにしてきましたが、いくつかの注意点があります。たとえば、翻訳する予定のWebサイト上のフレーズのリスト全体をユーザーが利用できるようにする必要があります。非表示にしているサイトの領域やアクセスを許可していないサイトの領域がある場合、これは問題になる可能性があります。

また、すべてのユーザーがサイトでJavascriptを使用して喜んでいると想定する必要がありますが、私の統計によると、ユーザーの約2.5%はJavascriptを使用せずに実行しています(またはNoscriptを使用してサイトでの使用をブロックしています)。 。

データベース駆動型の翻訳

PHPのデータベース接続速度は、書き直すことはできません。これは、翻訳するすべてのフレーズで関数を呼び出すというすでに高いオーバーヘッドに追加されます。このアプローチでは、パフォーマンスとスケーラビリティの問題が非常に大きくなります。


「フロントエンド翻訳」と混同しているようですが、私が意味したのは、翻訳された文字列を画面上で解析する方法でした。私はクライアント側でそれを翻訳する方法を絶対に探していません!フロントエンドで言語を切り替える最も簡単な方法を意味しましたが、それは明らかにCookieまたはユーザー設定を使用しています:)
Joshua-Pendo

ああ、そしてデータベース駆動で、私はすべての翻訳を管理する方法をもっと目指していたので、私の理想的な解決策は、データベースに翻訳を書き込むバックエンドであり、その後にPHPを生成する前処理コンポーネントを生成する関数が続くでしょうファイル。Why?:簡単です。テキストの小さな変更に煩わされたくないので、ユーザーはコードエディタやFTPプログラムを使用せずに自分でできるはずです:)
Joshua-Pendo

@PENDO私はあなたがフロントエンド翻訳を意味していないことを知っています、それはJSを使用してフロントエンド翻訳フレームワークを提案したユーザーへの薄いベールにされたコメントでした。;)
Glitch Desire

@PENDO同意します。あなたの提案どおりにバックエンドを使用しますが、パフォーマンス上の理由から、データベースの代わりにフラットファイルを使用します。あなたは置き換えることができますので、もちろん、ここではコアの提案は、変更時にプリレンダリングテンプレートがある.INIと3列のデータベーステーブルを使用してファイルをplaceholderreplacementlanguageplaceholderおよびの複合キーlanguage。次にtempfilemodified(テンプレートへのパス)と(DATETIME)を持つ別の2列があります。
Glitch Desire

1
@PENDOありがとうございます。私は250を元に戻しましたが、サイトで許可されたときに、テレスコに24時間以内にそれを授与する予定です。両方の答えを正解として選択したため、分割があなたの意図を最もよく表していると思います。
Glitch Desire

15

ホイールを発明せず、gettextとISO言語の略語リストを使用することをお勧めします。i18n / l10nが一般的なCMSまたはフレームワークにどのように実装されているかを見ましたか?

gettextを使用すると、複数の形式の数値のように、多くのケースがすでに実装されている強力なツールが得られます。英語では、単数形と複数形の2つのオプションしかありません。しかし、たとえばロシア語には3つの形式があり、英語ほど簡単ではありません。

また、多くの翻訳者は既にgettextを扱う経験があります。

見てくださいCakePHPのDrupalのを。多言語対応。インターフェイスのローカリゼーションの例としてのCakePHPとコンテンツ翻訳の例としてのDrupal。

l10nの場合、データベースの使用はまったく当てはまりません。クエリ数は膨大です。標準的なアプローチは、初期段階(または遅延読み込みを好む場合はi10n関数への最初の呼び出し)でメモリ内のすべてのl10nデータを取得することです。.poファイルまたはDBからすべてのデータを一度に読み取ることができます。そして、要求された文字列を配列から読み取るだけではありません。

インターフェースを翻訳するためのオンラインツールを実装する必要がある場合、すべてのデータをDBに保存できますが、すべてのデータをファイルに保存して操作できます。メモリ内のデータ量を削減するために、翻訳されたすべてのメッセージ/文字列をグループに分割し、可能な場合は必要なグループのみをロードすることができます。

だからあなたは完全にあなたの#3にいます。1つの例外を除いて、通常はコントローラごとのファイルなどではなく、1つの大きなファイルです。1つのファイルを開くことがパフォーマンスにとって最良だからです。高負荷のWebアプリの中には、include / requireが呼び出されたときにファイル操作を回避するためにすべてのPHPコードを1つのファイルにコンパイルするものがあることをご存じでしょう。

URLについて。Googleは間接的に翻訳の使用を提案します。

フランス語のコンテンツを明確に示すには:http : //example.ca/fr/vélo-de-montagne.html

また、私はあなたが接頭辞たとえば、既定の言語にユーザーをリダイレクトする必要があると思うhttp://examlpe.com/about-usにリダイレクトされますhttp://examlpe.com/en/about-us ますので、しかし、もしあなたのサイトの使用が唯一の言語接頭辞はまったく必要ありません。

チェックアウト: http: //www.audiomicro.com/trailer-hit-impact-psychodrama-sound-effects-836925 http://nl.audiomicro.com/aanhangwagen-hit-effect-psychodrama-geluidseffecten-836925 ます。http:/ /de.audiomicro.com/anhanger-hit-auswirkungen-psychodrama-sound-effekte-836925

コンテンツの翻訳はより難しい作業です。記事やメニュー項目など、コンテンツの種類によって多少の違いはあると思います。しかし、#4では正しい方法です。Drupalを調べて、アイデアを増やしてください。十分に明確なDBスキーマと翻訳に十分なインターフェースを備えています。あなたが記事を作成し、そのための言語を選択するように。そして、後でそれを他の言語に翻訳することができます。

Drupal変換インターフェース

URLスラッグには問題ないと思います。ナメクジ用に別のテーブルを作成するだけで、それは正しい判断になります。また、適切なインデックスを使用すると、大量のデータがあってもテーブルにクエリを実行することは問題になりません。そして、それは全文検索ではありませんでしたが、スラッグにvarcharデータ型を使用し、そのフィールドにインデックスを持つこともできる場合は、文字列が一致します。

PS申し訳ありませんが、私の英語は完璧ではありません。


私の質問に回答するのにかかった時間をありがとう。あなたの英語は私が理解するのに十分です!私はあなたの努力のためにあなたをすでに+1するつもりです!
ジョシュア-2013年

ヤロスラフ、もう一度、あなたの答えをありがとう。しかし、私は他の2つの回答に行きましたが、もう少し完全であり、コードを指摘する代わり、コードの背後で使用さているメソッドをすでに説明しています。
ジョシュア-ペンド

2
問題ない。確かにそれは私にとっても読みやすく、より完全で興味深い答えです。しかし、私もあなたが私の答えから何か有用なものを得たことを望みます。
Yaroslav

12

それはあなたのウェブサイトがどれだけのコンテンツを持っているかによります。最初は他のすべての人と同じようにデータベースを使用していましたが、データベースのすべての動作をスクリプト化するには時間がかかる場合があります。これが理想的な方法だとは言いませんが、特にテキストが多い場合はそうですが、データベースを使用せずに高速に実行したい場合は、この方法は機能しますが、ユーザーにデータの入力を許可することはできません。これは翻訳ファイルとして使用されます。しかし、自分で翻訳を追加すると、うまくいきます:

次のテキストがあるとします。

Welcome!

これを翻訳付きのデータベースに入力できますが、これを行うこともできます。

$welcome = array(
"English"=>"Welcome!",
"German"=>"Willkommen!",
"French"=>"Bienvenue!",
"Turkish"=>"Hoşgeldiniz!",
"Russian"=>"Добро пожаловать!",
"Dutch"=>"Welkom!",
"Swedish"=>"Välkommen!",
"Basque"=>"Ongietorri!",
"Spanish"=>"Bienvenito!"
"Welsh"=>"Croeso!");

これで、WebサイトでCookieを使用している場合、たとえば次のようになります。

$_COOKIE['language'];

簡単にするために、簡単に使用できるコードに変換してみましょう。

$language=$_COOKIE['language'];

Cookieの言語がウェールズ語で、次のコードがある場合:

echo $welcome[$language];

この結果は次のようになります。

Croeso!

ウェブサイトに多くの翻訳を追加する必要があり、データベースの消費が多すぎる場合は、配列を使用するのが理想的なソリューションです。


1
これは私が求めていた答えのどこにもありません。さらに、各ページですべての言語を使用できるようlang.en.phpにするの$lang['welcome']ではなく、各ファイルで宣言され、使用されているようなファイルを作成することをお勧めします。
ジョシュア-ペンド14

7

データベースを翻訳に依存しないことをお勧めします。それは本当に面倒な作業であり、データエンコードの場合は極端な問題になる可能性があります。

私は以前にも同様の問題に直面していて、私の問題を解決するために次のクラスを書きました

オブジェクト:Locale \ Locale

<?php

  namespace Locale;

  class Locale{

// Following array stolen from Zend Framework
public $country_to_locale = array(
    'AD' => 'ca_AD',
    'AE' => 'ar_AE',
    'AF' => 'fa_AF',
    'AG' => 'en_AG',
    'AI' => 'en_AI',
    'AL' => 'sq_AL',
    'AM' => 'hy_AM',
    'AN' => 'pap_AN',
    'AO' => 'pt_AO',
    'AQ' => 'und_AQ',
    'AR' => 'es_AR',
    'AS' => 'sm_AS',
    'AT' => 'de_AT',
    'AU' => 'en_AU',
    'AW' => 'nl_AW',
    'AX' => 'sv_AX',
    'AZ' => 'az_Latn_AZ',
    'BA' => 'bs_BA',
    'BB' => 'en_BB',
    'BD' => 'bn_BD',
    'BE' => 'nl_BE',
    'BF' => 'mos_BF',
    'BG' => 'bg_BG',
    'BH' => 'ar_BH',
    'BI' => 'rn_BI',
    'BJ' => 'fr_BJ',
    'BL' => 'fr_BL',
    'BM' => 'en_BM',
    'BN' => 'ms_BN',
    'BO' => 'es_BO',
    'BR' => 'pt_BR',
    'BS' => 'en_BS',
    'BT' => 'dz_BT',
    'BV' => 'und_BV',
    'BW' => 'en_BW',
    'BY' => 'be_BY',
    'BZ' => 'en_BZ',
    'CA' => 'en_CA',
    'CC' => 'ms_CC',
    'CD' => 'sw_CD',
    'CF' => 'fr_CF',
    'CG' => 'fr_CG',
    'CH' => 'de_CH',
    'CI' => 'fr_CI',
    'CK' => 'en_CK',
    'CL' => 'es_CL',
    'CM' => 'fr_CM',
    'CN' => 'zh_Hans_CN',
    'CO' => 'es_CO',
    'CR' => 'es_CR',
    'CU' => 'es_CU',
    'CV' => 'kea_CV',
    'CX' => 'en_CX',
    'CY' => 'el_CY',
    'CZ' => 'cs_CZ',
    'DE' => 'de_DE',
    'DJ' => 'aa_DJ',
    'DK' => 'da_DK',
    'DM' => 'en_DM',
    'DO' => 'es_DO',
    'DZ' => 'ar_DZ',
    'EC' => 'es_EC',
    'EE' => 'et_EE',
    'EG' => 'ar_EG',
    'EH' => 'ar_EH',
    'ER' => 'ti_ER',
    'ES' => 'es_ES',
    'ET' => 'en_ET',
    'FI' => 'fi_FI',
    'FJ' => 'hi_FJ',
    'FK' => 'en_FK',
    'FM' => 'chk_FM',
    'FO' => 'fo_FO',
    'FR' => 'fr_FR',
    'GA' => 'fr_GA',
    'GB' => 'en_GB',
    'GD' => 'en_GD',
    'GE' => 'ka_GE',
    'GF' => 'fr_GF',
    'GG' => 'en_GG',
    'GH' => 'ak_GH',
    'GI' => 'en_GI',
    'GL' => 'iu_GL',
    'GM' => 'en_GM',
    'GN' => 'fr_GN',
    'GP' => 'fr_GP',
    'GQ' => 'fan_GQ',
    'GR' => 'el_GR',
    'GS' => 'und_GS',
    'GT' => 'es_GT',
    'GU' => 'en_GU',
    'GW' => 'pt_GW',
    'GY' => 'en_GY',
    'HK' => 'zh_Hant_HK',
    'HM' => 'und_HM',
    'HN' => 'es_HN',
    'HR' => 'hr_HR',
    'HT' => 'ht_HT',
    'HU' => 'hu_HU',
    'ID' => 'id_ID',
    'IE' => 'en_IE',
    'IL' => 'he_IL',
    'IM' => 'en_IM',
    'IN' => 'hi_IN',
    'IO' => 'und_IO',
    'IQ' => 'ar_IQ',
    'IR' => 'fa_IR',
    'IS' => 'is_IS',
    'IT' => 'it_IT',
    'JE' => 'en_JE',
    'JM' => 'en_JM',
    'JO' => 'ar_JO',
    'JP' => 'ja_JP',
    'KE' => 'en_KE',
    'KG' => 'ky_Cyrl_KG',
    'KH' => 'km_KH',
    'KI' => 'en_KI',
    'KM' => 'ar_KM',
    'KN' => 'en_KN',
    'KP' => 'ko_KP',
    'KR' => 'ko_KR',
    'KW' => 'ar_KW',
    'KY' => 'en_KY',
    'KZ' => 'ru_KZ',
    'LA' => 'lo_LA',
    'LB' => 'ar_LB',
    'LC' => 'en_LC',
    'LI' => 'de_LI',
    'LK' => 'si_LK',
    'LR' => 'en_LR',
    'LS' => 'st_LS',
    'LT' => 'lt_LT',
    'LU' => 'fr_LU',
    'LV' => 'lv_LV',
    'LY' => 'ar_LY',
    'MA' => 'ar_MA',
    'MC' => 'fr_MC',
    'MD' => 'ro_MD',
    'ME' => 'sr_Latn_ME',
    'MF' => 'fr_MF',
    'MG' => 'mg_MG',
    'MH' => 'mh_MH',
    'MK' => 'mk_MK',
    'ML' => 'bm_ML',
    'MM' => 'my_MM',
    'MN' => 'mn_Cyrl_MN',
    'MO' => 'zh_Hant_MO',
    'MP' => 'en_MP',
    'MQ' => 'fr_MQ',
    'MR' => 'ar_MR',
    'MS' => 'en_MS',
    'MT' => 'mt_MT',
    'MU' => 'mfe_MU',
    'MV' => 'dv_MV',
    'MW' => 'ny_MW',
    'MX' => 'es_MX',
    'MY' => 'ms_MY',
    'MZ' => 'pt_MZ',
    'NA' => 'kj_NA',
    'NC' => 'fr_NC',
    'NE' => 'ha_Latn_NE',
    'NF' => 'en_NF',
    'NG' => 'en_NG',
    'NI' => 'es_NI',
    'NL' => 'nl_NL',
    'NO' => 'nb_NO',
    'NP' => 'ne_NP',
    'NR' => 'en_NR',
    'NU' => 'niu_NU',
    'NZ' => 'en_NZ',
    'OM' => 'ar_OM',
    'PA' => 'es_PA',
    'PE' => 'es_PE',
    'PF' => 'fr_PF',
    'PG' => 'tpi_PG',
    'PH' => 'fil_PH',
    'PK' => 'ur_PK',
    'PL' => 'pl_PL',
    'PM' => 'fr_PM',
    'PN' => 'en_PN',
    'PR' => 'es_PR',
    'PS' => 'ar_PS',
    'PT' => 'pt_PT',
    'PW' => 'pau_PW',
    'PY' => 'gn_PY',
    'QA' => 'ar_QA',
    'RE' => 'fr_RE',
    'RO' => 'ro_RO',
    'RS' => 'sr_Cyrl_RS',
    'RU' => 'ru_RU',
    'RW' => 'rw_RW',
    'SA' => 'ar_SA',
    'SB' => 'en_SB',
    'SC' => 'crs_SC',
    'SD' => 'ar_SD',
    'SE' => 'sv_SE',
    'SG' => 'en_SG',
    'SH' => 'en_SH',
    'SI' => 'sl_SI',
    'SJ' => 'nb_SJ',
    'SK' => 'sk_SK',
    'SL' => 'kri_SL',
    'SM' => 'it_SM',
    'SN' => 'fr_SN',
    'SO' => 'sw_SO',
    'SR' => 'srn_SR',
    'ST' => 'pt_ST',
    'SV' => 'es_SV',
    'SY' => 'ar_SY',
    'SZ' => 'en_SZ',
    'TC' => 'en_TC',
    'TD' => 'fr_TD',
    'TF' => 'und_TF',
    'TG' => 'fr_TG',
    'TH' => 'th_TH',
    'TJ' => 'tg_Cyrl_TJ',
    'TK' => 'tkl_TK',
    'TL' => 'pt_TL',
    'TM' => 'tk_TM',
    'TN' => 'ar_TN',
    'TO' => 'to_TO',
    'TR' => 'tr_TR',
    'TT' => 'en_TT',
    'TV' => 'tvl_TV',
    'TW' => 'zh_Hant_TW',
    'TZ' => 'sw_TZ',
    'UA' => 'uk_UA',
    'UG' => 'sw_UG',
    'UM' => 'en_UM',
    'US' => 'en_US',
    'UY' => 'es_UY',
    'UZ' => 'uz_Cyrl_UZ',
    'VA' => 'it_VA',
    'VC' => 'en_VC',
    'VE' => 'es_VE',
    'VG' => 'en_VG',
    'VI' => 'en_VI',
    'VN' => 'vn_VN',
    'VU' => 'bi_VU',
    'WF' => 'wls_WF',
    'WS' => 'sm_WS',
    'YE' => 'ar_YE',
    'YT' => 'swb_YT',
    'ZA' => 'en_ZA',
    'ZM' => 'en_ZM',
    'ZW' => 'sn_ZW'
);

/**
 * Store the transaltion for specific languages
 *
 * @var array
 */
protected $translation = array();

/**
 * Current locale
 *
 * @var string
 */
protected $locale;

/**
 * Default locale
 *
 * @var string
 */
protected $default_locale;

/**
 *
 * @var string
 */
protected $locale_dir;

/**
 * Construct.
 *
 *
 * @param string $locale_dir            
 */
public function __construct($locale_dir)
{
    $this->locale_dir = $locale_dir;
}

/**
 * Set the user define localte
 *
 * @param string $locale            
 */
public function setLocale($locale = null)
{
    $this->locale = $locale;

    return $this;
}

/**
 * Get the user define locale
 *
 * @return string
 */
public function getLocale()
{
    return $this->locale;
}

/**
 * Get the Default locale
 *
 * @return string
 */
public function getDefaultLocale()
{
    return $this->default_locale;
}

/**
 * Set the default locale
 *
 * @param string $locale            
 */
public function setDefaultLocale($locale)
{
    $this->default_locale = $locale;

    return $this;
}

/**
 * Determine if transltion exist or translation key exist
 *
 * @param string $locale            
 * @param string $key            
 * @return boolean
 */
public function hasTranslation($locale, $key = null)
{
    if (null == $key && isset($this->translation[$locale])) {
        return true;
    } elseif (isset($this->translation[$locale][$key])) {
        return true;
    }

    return false;
}

/**
 * Get the transltion for required locale or transtion for key
 *
 * @param string $locale            
 * @param string $key            
 * @return array
 */
public function getTranslation($locale, $key = null)
{
    if (null == $key && $this->hasTranslation($locale)) {
        return $this->translation[$locale];
    } elseif ($this->hasTranslation($locale, $key)) {
        return $this->translation[$locale][$key];
    }

    return array();
}

/**
 * Set the transtion for required locale
 *
 * @param string $locale
 *            Language code
 * @param string $trans
 *            translations array
 */
public function setTranslation($locale, $trans = array())
{
    $this->translation[$locale] = $trans;
}

/**
 * Remove transltions for required locale
 *
 * @param string $locale            
 */
public function removeTranslation($locale = null)
{
    if (null === $locale) {
        unset($this->translation);
    } else {
        unset($this->translation[$locale]);
    }
}

/**
 * Initialize locale
 *
 * @param string $locale            
 */
public function init($locale = null, $default_locale = null)
{
    // check if previously set locale exist or not
    $this->init_locale();
    if ($this->locale != null) {
        return;
    }

    if ($locale == null || (! preg_match('#^[a-z]+_[a-zA-Z_]+$#', $locale) && ! preg_match('#^[a-z]+_[a-zA-Z]+_[a-zA-Z_]+$#', $locale))) {
        $this->detectLocale();
    } else {
        $this->locale = $locale;
    }

    $this->init_locale();
}

/**
 * Attempt to autodetect locale
 *
 * @return void
 */
private function detectLocale()
{
    $locale = false;

    // GeoIP
    if (function_exists('geoip_country_code_by_name') && isset($_SERVER['REMOTE_ADDR'])) {

        $country = geoip_country_code_by_name($_SERVER['REMOTE_ADDR']);

        if ($country) {

            $locale = isset($this->country_to_locale[$country]) ? $this->country_to_locale[$country] : false;
        }
    }

    // Try detecting locale from browser headers
    if (! $locale) {

        if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {

            $languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);

            foreach ($languages as $lang) {

                $lang = str_replace('-', '_', trim($lang));

                if (strpos($lang, '_') === false) {

                    if (isset($this->country_to_locale[strtoupper($lang)])) {

                        $locale = $this->country_to_locale[strtoupper($lang)];
                    }
                } else {

                    $lang = explode('_', $lang);

                    if (count($lang) == 3) {
                        // language_Encoding_COUNTRY
                        $this->locale = strtolower($lang[0]) . ucfirst($lang[1]) . strtoupper($lang[2]);
                    } else {
                        // language_COUNTRY
                        $this->locale = strtolower($lang[0]) . strtoupper($lang[1]);
                    }

                    return;
                }
            }
        }
    }

    // Resort to default locale specified in config file
    if (! $locale) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Check if config for selected locale exists
 *
 * @return void
 */
private function init_locale()
{
    if (! file_exists(sprintf('%s/%s.php', $this->locale_dir, $this->locale))) {
        $this->locale = $this->default_locale;
    }
}

/**
 * Load a Transtion into array
 *
 * @return void
 */
private function loadTranslation($locale = null, $force = false)
{
    if ($locale == null)
        $locale = $this->locale;

    if (! $this->hasTranslation($locale)) {
        $this->setTranslation($locale, include (sprintf('%s/%s.php', $this->locale_dir, $locale)));
    }
}

/**
 * Translate a key
 *
 * @param
 *            string Key to be translated
 * @param
 *            string optional arguments
 * @return string
 */
public function translate($key)
{
    $this->init();
    $this->loadTranslation($this->locale);

    if (! $this->hasTranslation($this->locale, $key)) {

        if ($this->locale !== $this->default_locale) {

            $this->loadTranslation($this->default_locale);

            if ($this->hasTranslation($this->default_locale, $key)) {

                $translation = $this->getTranslation($this->default_locale, $key);
            } else {
                // return key as it is or log error here
                return $key;
            }
        } else {
            return $key;
        }
    } else {
        $translation = $this->getTranslation($this->locale, $key);
    }
    // Replace arguments
    if (false !== strpos($translation, '{a:')) {
        $replace = array();
        $args = func_get_args();
        for ($i = 1, $max = count($args); $i < $max; $i ++) {
            $replace['{a:' . $i . '}'] = $args[$i];
        }
        // interpolate replacement values into the messsage then return
        return strtr($translation, $replace);
    }

    return $translation;
  }
}

使用法

 <?php
    ## /locale/en.php

    return array(
       'name' => 'Hello {a:1}'
       'name_full' => 'Hello {a:1} {a:2}'
   );

$locale = new Locale(__DIR__ . '/locale');
$locale->setLocale('en');// load en.php from locale dir
//want to work with auto detection comment $locale->setLocale('en');

echo $locale->translate('name', 'Foo');
echo $locale->translate('name', 'Foo', 'Bar');

使い方

{a:1} メソッドに渡される最初の引数に置き換えられます Locale::translate('key_name','arg1') {a:2}れる2番目の引数に置き換えられますLocale::translate('key_name','arg1','arg2')

検出のしくみ

  • デフォルトでは、geoipがインストールされgeoip_country_code_by_nameている場合、国コードが返され、geoipがインストールされていない場合、HTTP_ACCEPT_LANGUAGEヘッダーへのフォールバック

データベースはどのように乱雑になりますか?異なる言語で可能な文字のため?これまでのところ、私は主に英語、フランス語、オランダ語、ドイツ語のウェブサイトを持っているので、今のところ問題はありません。答えをありがとう、しかしそれは答えのほんの一部なので、賞金を獲得することはできません。
ジョシュア-2013年

よく私はあなたの質問があなたにのみ役立つと思います必要な言語に対してヒンディー語、タイ語、中国語、アラビア語(これらの言語は文字を表現するために1バイト以上かかる)のような言語の使用を検討する人がいるでしょう dbを使用している場合は、utf8_general_ci照合が適切な方法です。
Shushant 2013年

私は同意します、私はそこに少しトラックを手に入れました。指摘していただきありがとうございます。マルチビット文字もこの質問で言及するのに十分重要です:)
ジョシュア-ペンド

5

副次的な答え:翻訳されたURLの前に言語識別子を付けて使用する:http : //www.domain.com/nl/over-ons
ハイブリッドソリューションは複雑になる傾向があるため、そのまま使用します。どうして?原因はURLがSEOに不可欠です。

db変換について:言語の数は多かれ少なかれ固定されていますか?それとも、予測不可能でダイナミックですか?修正されている場合は、新しい列を追加するだけです。それ以外の場合は、複数のテーブルを使用します。

しかし、一般的に、なぜDrupalを使用しないのですか?私は誰もが独自のCMSを構築したいと思っていることを知っています。なぜなら、それがより速く、よりスリムになるなどです。しかし、それは本当に悪い考えです!


1
ご回答有難うございます。Drupal / Joomlaを使用したくない理由は単純です。システムのすべての内外、明らかな欠陥、コードのビルド方法(そして重要な点:300人のプログラマーが一緒にビルドしないこと)を確実に確認したい。オープンソースを選択しない理由は十分にあります。それだけでなく、私の会社がクライアントにとって重要な要素になることを望んでいます。彼らが他の開発者のところに行って何も残してくれないのは悪いことです。
ジョシュア-2013年

7
私はこれらすべての理由がたくさんの記事で議論されていると思います。あなたの顧客はあなたが他の誰も維持できない独占的なCMSを持っているのであなたが正確にあなたを選ばないことを望みます。しかし、とにかく、それは全く異なる議論です。
レミー

1
私はあなたの要点を理解しました、それでも私はすべての内外を知っているシステムを好みます、そして私がプラグインを使用するとき他の誰かの仕事に依存することに何も感じていません。
ジョシュア-ペンド

1
その上、私は自分の仕事を十分に文書化する傾向があります。私が「一人の軍」の人なので、私のために働いてシステムを知るのに苦労することはないはずです。
ジョシュア-ペンド

悪い考えはDrupalを選ぶことであり、グーグルでさえ、URLが翻訳されているかどうかは気にしないと言っています。ただし、ロケール識別子を含める必要があります。
undefinedman

5

私はすでに与えられた答えを洗練しようとするつもりはありません。代わりに、私自身のOOP PHPフレームワークが翻訳を処理する方法について説明します。

内部的には、私のフレームワークはen、fr、es、cnなどのコードを使用しています。配列は、ウェブサイトでサポートされている言語を保持します:array( 'en'、 'fr'、 'es'、 'cn')言語コードは$ _GET(lang = fr)を介して渡されます。配列の最初の言語に設定されます。そのため、プログラムの実行中いつでも、最初から現在の言語がわかっています。

一般的なアプリケーションで翻訳する必要があるコンテンツの種類を理解しておくと役立ちます。

1)クラス(または手続き型コード)からのエラーメッセージ2)クラス(または手続き型コード)からの非エラーメッセージ3)ページコンテンツ(通常はデータベースに保存)4)サイト全体の文字列(ウェブサイト名など)5)スクリプト-特定の文字列

最初のタイプは理解するのが簡単です。基本的に、「データベースに接続できませんでした...」のようなメッセージについて話します。これらのメッセージは、エラーが発生したときにのみロードする必要があります。私のマネージャークラスは他のクラスからの呼び出しを受け取り、パラメーターとして渡された情報を使用して、関連するクラスフォルダーに移動し、エラーファイルを取得します。

2番目のタイプのエラーメッセージは、フォームの検証が失敗したときに表示されるメッセージに似ています。(「...は空白にできません」または「5文字を超えるパスワードを選択してください」)。クラスを実行する前に文字列をロードする必要があります。

実際のページコンテンツでは、言語ごとに1つのテーブルを使用します。各テーブルには、言語のコードがプレフィックスとして付けられています。したがって、en_contentは英語のコンテンツを含むテーブルであり、es_contentはスペイン向け、cn_contentは中国向け、fr_contentはフランス語向けです。

4番目の種類の文字列は、Webサイト全体に関連しています。これは、言語のコードを使用して名前が付けられた構成ファイル、つまりen_lang.php、es_lang.phpなどを介してロードされます。グローバル言語ファイルでは、array( 'English'、 'C​​hinese'、 'Spanish'、 'French')などの翻訳された言語を英語のグローバルファイルとarray( 'Anglais'、 'C​​hinois'、 'に読み込む必要があります。 Espagnol '、' Francais ')をフランス語ファイルで使用します。したがって、言語を選択するためのドロップダウンを入力すると、それは正しい言語です;)

最後に、スクリプト固有の文字列があります。したがって、調理アプリケーションを作成すると、「オーブンは十分に熱くなりませんでした」のようになります。

私のアプリケーションサイクルでは、グローバル言語ファイルが最初にロードされます。そこには、グローバルストリング(「Jack's Website」など)だけでなく、一部のクラスの設定もあります。基本的に、言語や文化に依存するものは何でも。そこにある文字列には、日付のマスク(MMDDYYYYまたはDDMMYYYY)、またはISO言語コードが含まれています。メイン言語ファイルには、クラスが非常に少ないため、個々のクラスの文字列を含めています。

ディスクから読み取られる2番目と最後の言語ファイルは、スクリプト言語ファイルです。lang_en_home_welcome.phpは、home / welcomeスクリプトの言語ファイルです。スクリプトは、モード(ホーム)とアクション(ようこそ)によって定義されます。各スクリプトには、configファイルとlangファイルを含む独自のフォルダーがあります。

スクリプトは、上記で説明したように、コンテンツテーブルに名前を付けるデータベースからコンテンツをプルします。

何か問題が発生した場合、マネージャーは言語依存のエラーファイルを取得する場所を知っています。そのファイルは、エラーが発生した場合にのみロードされます。

結論は明白です。アプリケーションまたはフレームワークの開発を始める前に、翻訳の問題について考えてください。また、翻訳を組み込んだ開発ワークフローも必要です。私のフレームワークでは、サイト全体を英語で開発し、関連するすべてのファイルを翻訳しています。

翻訳文字列の実装方法についての簡単な最後の言葉です。私のフレームワークには、他のサービスで利用可能なサービスを実行する単一のグローバル$ managerがあります。したがって、たとえば、フォームサービスはhtmlサービスを取得し、それを使用してhtmlを記述します。私のシステムのサービスの1つは翻訳サービスです。$ translator-> set($ service、$ code、$ string)は、現在の言語の文字列を設定します。言語ファイルは、そのようなステートメントのリストです。$ translator-> get($ service、$ code)は翻訳文字列を取得します。$ codeは、1のような数値、または 'no_connection'のような文字列にすることができます。各サービスは、トランスレータのデータ領域に独自の名前空間を持っているため、サービス間の衝突はありません。

私がここに投稿するのは、数年前に私がしなければならなかったように、誰かがホイールを再発明するというタスクを救うことを期待して。


4

Symfonyフレームワークの使用を開始する前に、私はしばらく前に同じ問題を抱えていました。

  1. 引数としてpageId(または#2で説明したobjectId、objectTable)、ターゲット言語、およびフォールバック(デフォルト)言語のオプションのパラメーターを持つ関数__()を使用するだけです。デフォルトの言語は、後で簡単に変更するために、いくつかのグローバル構成で設定できます。

  2. データベースにコンテンツを保存するために、私は次の構造を使用しました:(pageId、言語、コンテンツ、変数)。

    • pageIdは、翻訳するページのFKです。ニュース、ギャラリーなど、他のオブジェクトがある場合は、objectId、objectTableの2つのフィールドに分割します。

    • 言語-明らかに、ISO言語文字列EN_en、LT_lt、EN_usなどを格納します。

    • content-変数の置換のためにワイルドカードと一緒に翻訳したいテキスト。例「こんにちは、%% name %%。アカウントの残高は%% balance %%です。」

    • variables-jsonでエンコードされた変数。PHPは、これらをすばやく解析するための関数を提供します。例:「名前:Laurynas、残高:15.23」。

    • あなたはスラグフィールドについても言及しました。この表に自由に追加して、すばやく検索することができます。

  3. 翻訳をキャッシュすることで、データベース呼び出しを最小限に抑える必要があります。PHP言語で最速の構造であるため、PHP配列に格納する必要があります。このキャッシングをどのように行うかはあなた次第です。私の経験から、サポートされている各言語のフォルダーと各pageIdの配列が必要です。翻訳を更新した後、キャッシュを再構築する必要があります。変更された配列のみを再生成する必要があります。

  4. #2で答えたと思う

  5. あなたの考えは完全に論理的です。これはかなりシンプルで問題はないと思います。

URLは、変換テーブルに格納されているスラッグを使用して変換する必要があります。

最後の言葉

ベストプラクティスを研究することは常に良いことですが、車輪を再発明しないでください。よく知られているフレームワークからコンポーネントを取り出して使用するだけです。

symfonyの翻訳コンポーネントを見てください。それはあなたにとって良いコードベースになるでしょう。


コメントをありがとう、あなたの時間を+1してください。私の場合、Laravel(私の場合)はSymfonyの一部を使用しています。私はこの質問(と報奨金)を開始して、他の人が翻訳を行う方法について洞察を得ました。そこには多くのベストプラクティスがあると信じ始めています:-)
ジョシュア-ペンド

1

私は何度も何度も何度も自分自身に関連する質問をしてきましたが、正式な言語で迷子になりました...

高度なCMSを確認することをお勧めします

Typo3のためにPHP (私はたくさんのものがあることを知っていますが、それが私が最も成熟していると思うものです)

PlonePython

2013年のWebの動作が異なる場合は、ゼロから始めてください。それは、高度なスキルを持つ/経験豊富な人々のチームをまとめて新しいCMSを構築することを意味します。その目的のためにポリマーを見てみたいと思うかもしれません。

コーディングと多言語のウェブサイト/ネイティブ言語のサポートに関しては、すべてのプログラマーがユニコードについての手がかりを持つべきだと思います。あなたがユニコードを知らなければ、あなたは間違いなくあなたのデータを台無しにするでしょう。何千ものISOコードを使用しないでください。彼らはあなたにいくつかのメモリを節約します。しかし、UTF-8を使用すれば、文字をすべて漢字で保存することもできます。しかし、そのためには、基本的にutf-16またはutf-32にする2バイトまたは4バイトの文字を格納する必要があります。

URLエンコーディングについての場合も、エンコーディングを混在させないでください。少なくともドメイン名には、ブラウザのようなアプリケーションを提供するさまざまなロビーによって定義されたルールがあることに注意してください。たとえば、ドメインは次のように非常に似ています。

ьankofamerica.comまたはbankofamerica.com samesamebutdifferent;)

もちろん、すべてのエンコーディングで機能するファイルシステムが必要です。utf-8ファイルシステムを使用するUnicodeのもう1つの利点。

翻訳については、ドキュメントの構造について考えてください。本や記事など。docbookそれらの構造について理解するための仕様があります。しかし、HTMLでは、コンテンツブロックについてのみです。したがって、そのレベルで、またWebページレベルまたはドメインレベルでの翻訳が必要です。したがって、ブロックが存在しない場合はそこに存在しません。Webページが存在しない場合は、上位のナビゲーションレベルにリダイレクトされます。ドメインのナビゲーション構造が完全に異なる場合、管理する完全に異なる構造です。これはすでにTypo3でできています。

フレームワークについては、私が知っている最も成熟したフレームワークであり、MVC(流行語)のような一般的なことをするために私は本当に嫌いです。「パフォーマンス」のように何かを販売したい場合は、パフォーマンスとフィーチャーリッチという言葉を使用して販売します...地獄)はZendです。php chaosコーダーに標準を導入することは良いことであることが証明されています。ただし、typo3にはCMSの他にフレームワークもあります。最近再開発され、現在はflow3と呼ばれています。もちろん、フレームワークはデータベースの抽象化、テンプレート化、キャッシングの概念をカバーしていますが、それぞれに長所があります。

キャッシングについてなら...それは驚くほど複雑/多層化する可能性があります。PHPでは、アクセラレータ、オペコードだけでなく、html、httpd、mysql、xml、css、jsなどのあらゆる種類のキャッシュについても考えます。もちろん、一部の部分はキャッシュする必要があり、ブログの回答のような動的な部分はキャッシュしてはなりません。一部は、生成されたURLを使用してAJAX経由でリクエストする必要があります。JSON、ハッシュバングなど

次に、特定のユーザーのみがアクセスまたは管理できるように、ウェブサイト上の小さなコンポーネントを配置したいので、概念的にはそれが大きな役割を果たします。

また、あなたがしたいのですが、統計を使用すると、データベースの異なるタイプの必要があるので、多分...システム/ facebooksなどのFacebookのあなたの上にトップのCMSの上に構築される任意のソフトウェア配布し、インメモリ、bigdata、XML、何を。

まあ、今のところそれで十分だと思います。typo3 / ploneや言及されたフレームワークのいずれも聞いたことがない場合は、勉強するだけで十分です。その道筋には、まだ尋ねていない質問に対する多くの解決策があります。

2013年とphpがとにかく死ぬので、新しいCMSを作ってみましょう。他の開発者グループに参加して、行方不明にならないようにしてください。

幸運を!

そしてところで。人々が将来ウェブサイトをもう持たないのはどうですか?みんなGoogle+に参加するの?開発者がもう少し創造的になり、何か便利なことをしてくれることを願っています(ボーグルに同化しないように)

////編集///既存のアプリケーションについて少し考えます:

PHP mysql CMSがあり、多言語サポートを組み込みたい場合。任意の言語の追加の列を持つテーブルを使用するか、同じテーブルにオブジェクトIDと言語IDを使用して翻訳を挿入するか、任意の言語の同じテーブルを作成してそこにオブジェクトを挿入し、必要に応じて選択ユニオンを作成します。それらすべてを表示させるには データベースにはutf8の一般的なciを使用し、もちろんフロント/バックエンドではutf8 text / encodingを使用します。私はあなたがすでに説明した方法でURLにURLパスセグメントを使用しました

domain.org/en/aboutでは、lang IDをコンテンツテーブルにマッピングできます。とにかく、あなたはあなたのURLのためのパラメータのマップを持っている必要があるので、あなたはあなたのURLのパスセグメントからマッピングされるパラメータを定義したいでしょう。

domain.org/en/about/employees/IT/administrators/

ルックアップ構成

pageid | url

1 | /about/employees/../ ..

1 | /../about/employees../../

パラメータをURLパスセグメント ""にマップします

$parameterlist[lang] = array(0=>"nl",1=>"en"); // default nl if 0
$parameterlist[branch] = array(1=>"IT",2=>"DESIGN"); // default nl if 0
$parameterlist[employertype] = array(1=>"admin",1=>"engineer"); //could be a sql result 

$websiteconfig[]=$userwhatever;
$websiteconfig[]=$parameterlist;
$someparameterlist[] = array("branch"=>$someid);
$someparameterlist[] = array("employertype"=>$someid);
function getURL($someparameterlist){ 
// todo foreach someparameter lookup pathsegment 
return path;
}

言うまでもなく、それはすでに上部の記事でカバーされています。

そして、忘れないように、ほとんどの場合index.phpになる生成中のphpファイルのURLを「書き換える」必要があります。


コメントをありがとう、私が考えるべきことは確かにあります。私はもう2年間utf8エンコーディングを使用していますが、以前は文字と格闘していました;-)反対に、CMS /フレームワークのタイプは、まるでゼロからコーディングしているかのように、プラットフォームに依存しない方法。
ジョシュア-ペンド

本当に最初からコーディングしたい場合は、DartlangとPolymerを確認することをお勧めします。dartlangはブラウザで動作しており、32ビットと64ビットのサポートがあり、サーバーサイドでほとんどの目的に使用でき、dart2jsコンパイラがあるので、調査する価値があります。人々がプラットフォームの独立性について話すならば、彼らはジャバについて考えます...それが何を意味するか私たちは知っています。Buildprocess ...交換にはJSONを使用すると思います。ハッシュバングとサーバーサイドで生成されたクライアントサイドのWebサイト。
Dama博士2013年

データベースレイアウトと生成ロジックが主なタスクです。誰もあなたのためにここでそうするつもりはありません...しかし、アイデア自体が重要です。私はロビーのことは気にしないので、物事を成し遂げるために、モデルを作成していくつかのものを共有していただければ幸いです。現在、同様の作業を行っています。しかし、私はまだプレーニング中です。Typo3をバックエンドとして検討し、新しいクライアント構造を作成しています。多言語パターンはバックエンドで解決され、検索エンジン/ウェブサービス専用の方法で情報を共有します。とにかく、すべての状況依存型で継続的な構築タスク
Dr Dama Dama

-1

データベース作業:

言語テーブル「言語」を作成します。

田畑:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

データベース「コンテンツ」にテーブルを作成します。

田畑:

content_id(primary and auto incremented)

main_content

header_content

footer_content

leftsidebar_content

rightsidebar_content

language_id(foreign key: referenced to languages table)

created_at

created_by

updated_at

updated_by

フロントエンド作業:

ユーザーがドロップダウンまたは任意の領域から言語を選択すると、選択した言語IDがセッションに保存されます。

$_SESSION['language']=1;

セッションに保存されている言語IDに基づいて、データベーステーブル 'content'からデータをフェッチします。

詳細はこちら http://skillrow.com/multilingual-website-in-php-2/


1
これは、必要な単純な言語統合の方法です。投稿全体と回答を読んでみましたか?
ジョシュア-ペンド

-2

ほとんどすべてのサイトがフランス語と英語であるケベックに住んでいる人として...私はWPのためのほとんどの多言語プラグインとは言わないまでも多くのことを試みてきました...私は生きて、それで死にます!

https://wordpress.org/plugins/mqtranslate/


1
そうですね、WPは問題の要因ではありませんでした。
ジョシュア-

-3

何についてWORDPRESS + MULTI-LANGUAGE SITE BASIS(プラグイン)?サイトの構造は次のとおりです。

  • example.com/ eng / category1 / ....
  • example.com/ eng / my-page ....
  • example.com/ rus / category1 / ....
  • example.com/ rus / my-page ....

プラグインは、シンプルなロジックですべてのフレーズを翻訳するためのインターフェースを提供します。

(ENG) my_title - "Hello user"
(SPA) my_title - "Holla usuario"

それからそれは出力することができます:
echo translate('my_title', LNG); // LNG is auto-detected

ただし、プラグインがまだアクティブかどうかを確認してください。


3
スペイン語で「Holla userio」ではない「Hola
Usuario

1
Lol Holla userio、それは面白かった!
spekdrum 2017年

私がスペイン語を知らなかった理由(ちょうど使用された例)、ダウン投票するために急いでください!! :)
T.Todua 2017年

-5

JavaScriptをアップロードできるWebサイトで機能する本当にシンプルなオプションはwww.multilingualizer.comです。

すべての言語のすべてのテキストを1つのページに配置し、ユーザーが見る必要のない言語を非表示にすることができます。うまくいきます。


注意してください、SEOは非常に悪いでしょう!さらに、一部だけが必要なときにすべてのコンテンツをロードしますが、これは本当に悪い習慣です。
Hafenkranich、2018年

サイトが英語のみであるという奇妙なもの...なぜ彼らは彼らの解決策を使用しないのですか?
eduardo.lopes 2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.