ベストプラクティスの多言語Webサイト

182
Joshua - Pendo 2013-10-09 03:33.

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

まず、私が探している状況をスケッチしましょう

かなり前から使っているコンテンツ管理システムをアップグレード/再開発しようとしています。しかし、多言語はこのシステムの大きな改善だと感じています。以前はフレームワークを使用していませんでしたが、次のプロジェクトでは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は最終的にはさらに高速に解析されます。このfouldには、のデータが含まれている必要がありますformat parameter=value; 。レンダリングされる各ビューには、存在する場合は独自の言語ファイルを含めることができるため、これがこれを行うための最良の方法だと思います。次に、言語パラメーターをグローバルスコープではなく特定のビューにロードして、パラメーターが相互に上書きされないようにする必要があります。
  4. データベーステーブルの変換:これは実際、私が最も心配していることです。ニュース/ページなどの翻訳を作成する方法を探しています。できるだけ早く。モジュールごとに2つのテーブル(たとえばNewsNews_translations)を用意することはオプションですが、優れたシステムを取得するために多くの作業を行う必要があるように感じます。私が思いついたものの一つが基づいているdata versioning私が書いたシステム:1人のデータベーステーブル名がありTranslations、このテーブルはのユニークな組み合わせを持っているlanguagetablenameprimarykey。例:en_En / News / 1(ID = 1のニュースアイテムの英語版を参照)。ただし、この方法には2つの大きな欠点があります。まず、このテーブルはデータベースに大量のデータがあるとかなり長くなる傾向があり、次に、この設定を使用してテーブルを検索するのは大変な作業になります。たとえば、アイテムのSEOスラッグを検索すると、全文検索になりますが、これはかなり馬鹿げています。しかし一方で、これはすべてのテーブルで翻訳可能なコンテンツを非常に高速に作成するための迅速な方法ですが、このプロが短所を過大評価しているとは思いません。
  5. フロントエンドの作業:フロントエンドにもいくつかの考慮が必要です。もちろん、利用可能な言語をデータベースに保存し、必要な言語を(非)アクティブにします。このようにして、スクリプトは言語を選択するためのドロップダウンを生成でき、バックエンドはCMSを使用して実行できる翻訳を自動的に決定できます。選択した言語(en_ENなど)は、ビューの言語ファイルを取得するとき、またはWebサイトのコンテンツアイテムの正しい翻訳を取得するときに使用されます。

だから、彼らはいます。これまでの私の考え。日付などのローカリゼーションオプションもまだ含まれていませんが、私のサーバーはPHP5.3.2 +をサポートしているため、ここで説明するように、intl拡張機能を使用するのが最善のオプションです:http://devzone.zend.com/1500/internationalization-in -php-53 / -しかし、これは後の開発スタジアムで役立ちます。今のところ、主な問題は、ウェブサイトのコンテンツの翻訳をどのように実践するかです。

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

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-us...私の目には、これが最善の解決策です。CMSが1つの言語のみに設定されている場合、URLにこの言語IDを含める必要がないためです。

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

私の質問があなたの頭を割ってくれることを願っています、彼らは確かに私のものを割ったのです!それは私がここで質問として物事を解決するのをすでに助けました。以前に使用した方法と、今後のCMSで考えているアイデアを確認する可能性がありました。

このたくさんのテキストをお読みいただき、ありがとうございました。

// Edit #1

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

9 answers

118
tereško 2013-10-18 05:31.

トピックの前提

多言語サイトには、次の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つまたは0の可能性があります。正しいオプションを見つけるには、Cookieやヘッダーを使用する必要があります。

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

パラメータとしての言語

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

言語の二次情報源もあります:クッキー値。ただし、「コールドスタート」(ユーザーが初めてカスタムクエリでサイトを開いたとき)の場合、可能な言語の数が不明なため、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 );

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

51
Glitch Desire 2013-10-18 01:29.

Thomas Bleyが提案したように、プリプロセッサを使用してパフォーマンスヒットなしでi18nを実装する

職場では、最近、いくつかのプロパティでi18nの実装を行いましたが、苦労し続けたのは、オンザフライ翻訳を処理することによるパフォーマンスの低下でした。その後、ThomasBleyによるこのすばらしいブログ投稿を発見しました。これは、パフォーマンスの問題を最小限に抑えて大量のトラフィック負荷を処理するために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または=で取得して、CMSがINIファイルに書き込めるようにするのは簡単です。

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

基本的に、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;
}

注:正規表現が機能することを確認していません。会社のサーバーからコピーしていませんが、操作がどのように機能するかを確認できます。

それを呼び出す方法

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

// 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万人の訪問者は、翻訳の置換を1回だけ実行することになります)。
  2. 単純なフラットファイルを使用し、純粋なPHPソリューションであるため、データベースに負荷をかけることはありません。
  3. 翻訳内でPHP式を使用する機能。

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

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

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

私たちの記事には複合主キーがidありlanguage、記事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のデータベース接続速度については何も言えません。これにより、翻訳するすべてのフレーズで関数を呼び出すという、すでに高いオーバーヘッドが追加されます。このアプローチでは、パフォーマンスとスケーラビリティの問題が圧倒されるようです。

15
Yaroslav 2013-10-11 17:05.

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

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

また、多くの翻訳者はすでにgettextを使用した経験があります。

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

l10nの場合、データベースの使用はまったく当てはまりません。クエリは大量になります。標準的なアプローチは、すべてのl10nデータを初期段階で(または遅延読み込みが必要な場合はi10n関数の最初の呼び出し中に)メモリに取得することです。.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-ushttp://examlpe.com/en/about-usにリダイレクトし ますただし、サイトで1つの言語しか使用していない場合は、プレフィックスはまったく必要ありません。

チェックアウト: 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スキーマと、翻訳に十分なインターフェイスがあります。あなたが記事を作成し、そのための言語を選択するように。そして、後でそれを他の言語に翻訳することができます。

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

PS申し訳ありませんが、私の英語は完璧にはほど遠いです。

12
user3749746 2014-07-09 07:31.

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

あなたがこのテキストを持っているとしましょう:

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!

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

7
Shushant 2013-10-16 21:15.

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

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

オブジェクト: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ヘッダーへのフォールバックを返します
5
Remy 2013-10-09 21:08.

ただのサブアンサー:言語識別子が前に付いた翻訳済みURLを絶対に使用してくださいhttp//www.domain.com/nl/over-ons
ハイブリッドソリューションは複雑になる傾向があるので、私はそれに固執します。どうして?URLはSEOに不可欠です。

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

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

5
JG Estiot 2015-03-16 19:03.

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

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

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

1)クラス(または手続き型コード)からのエラーメッセージ2)クラス(または手続き型コード)からの非エラーメッセージ3)ページコンテンツ(通常はデータベースに保存)4)サイト全体の文字列(Webサイト名など)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($サービス、$code,$string)現在の言語の文字列を設定します。言語ファイルは、そのようなステートメントのリストです。$translator->get($サービス、$code) retrieves a translation string. The $コードは、1のような数値、または「no_connection」のような文字列にすることができます。各サービスはトランスレータのデータ領域に独自の名前空間を持っているため、サービス間で衝突が発生することはありません。

数年前にやらなければならなかったように、誰かが車輪の再発明をする手間を省けることを願って、これをここに投稿します。

4
Laurynas Mališauskas 2013-10-13 00:54.

Symfonyフレームワークを使い始める前に、私はしばらく前に同じプローブを持っていました。

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

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

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

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

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

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

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

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

  4. #2で答えたと思います

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

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

最後の言葉

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

Symfonyの翻訳コンポーネントを見てください。それはあなたにとって良いコードベースかもしれません。

1
Dr. Dama 2013-10-18 16:00.

私は何度も何度も自分自身に関連する質問をしていましたが、正式な言語で迷子になりました...しかし、少しだけあなたを助けるために、いくつかの発見を共有したいと思います:

高度なCMSをご覧になることをお勧めします

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

PlonePython

2013年のWebの動作が異なるはずであることがわかった場合は、最初から始めてください。これは、高度なスキルと経験を積んだ人々のチームを編成して、新しいCMSを構築することを意味します。その目的のためにポリマーを見てみたいと思うかもしれません。

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

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

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

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

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

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

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

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

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

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

2013年とphpがとにかく死にかけているので、新しいCMSを作成しようと思ったら、迷子にならないように他の開発者グループに参加することを歓迎します。

幸運を!

そしてところで。将来、ウェブサイトがなくなるのはどうですか?そして、私たちは皆google +にいますか?開発者がもう少し創造的になり、何か役に立つことをしてくれることを願っています(ボーグルに同化されないように)

////編集///既存のアプリケーションについて少し考えてみましょう。

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

domain.org/en/aboutでは、langIDをコンテンツテーブルにマッピングできます。とにかく、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を「書き直す」必要があるでしょう

Related questions

MORE COOL STUFF

「水曜日」シーズン1の中心には大きなミステリーがあります

「水曜日」シーズン1の中心には大きなミステリーがあります

Netflixの「水曜日」は、典型的な10代のドラマ以上のものであり、実際、シーズン1にはその中心に大きなミステリーがあります.

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ボディーランゲージの専門家は、州訪問中にカミラ・パーカー・ボウルズが輝くことを可能にした微妙なケイト・ミドルトンの動きを指摘しています

ケイト・ミドルトンは、州の夕食会と州の訪問中にカミラ・パーカー・ボウルズからスポットライトを奪いたくなかった、と専門家は言う.

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンがハリー・スタイルズとオリビア・ワイルドの「非常に友好的な」休憩が永続的であることを望んでいる理由

一部のファンが、オリビア・ワイルドが彼女とハリー・スタイルズとの間の「難しい」が「非常に友好的」な分割を恒久的にすることを望んでいる理由を見つけてください.

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする 

エリザベス女王の死後、ケイト・ミドルトンはまだ「非常に困難な時期」を過ごしている、と王室の専門家が明らかにする&nbsp;

エリザベス女王の死後、ケイト・ミドルトンが舞台裏で「非常に困難な時期」を過ごしていたと伝えられている理由を調べてください.

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セントヘレナのジェイコブのはしごを登るのは、気弱な人向けではありません

セント ヘレナ島のジェイコブズ ラダーは 699 段の真っ直ぐ上る階段で、頂上に到達すると証明書が発行されるほどの難易度です。

The Secrets of Airline Travel Quiz

The Secrets of Airline Travel Quiz

Air travel is far more than getting from point A to point B safely. How much do you know about the million little details that go into flying on airplanes?

Where in the World Are You? Take our GeoGuesser Quiz

Where in the World Are You? Take our GeoGuesser Quiz

The world is a huge place, yet some GeoGuessr players know locations in mere seconds. Are you one of GeoGuessr's gifted elite? Take our quiz to find out!

バイオニック読書はあなたをより速く読むことができますか?

バイオニック読書はあなたをより速く読むことができますか?

BionicReadingアプリの人気が爆発的に高まっています。しかし、それは本当にあなたを速読術にすることができますか?

Total War:Warhammer:Kotakuレビュー

Total War:Warhammer:Kotakuレビュー

私はこのゲームを嫌う準備ができていました。先週の前に、Total War:Warhammerについての私の考えがありました:それでもここに私は、私の手にある完成品であり、私は変わった男です。

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

涙の道:軍事化された帝国主義勢力がスタンディングロックキャンプを占領

スタンディングロックスー族のメンバーと水の保護者は、ノースダコタ州のスタンディングロックにあるオセティサコウィンキャンプを去ります。(Twitter経由のCNNスクリーンショット)火と煙がスカイラインを覆い、スタンディングロックスー族のメンバーと水の保護者が、聖なるものを守りながら建てた家、オセティサコウィン(セブンカウンシルファイアーズ)キャンプから行進し、太鼓を打ち、歌い、祈りました。ダコタアクセスパイプラインとしても知られる「ブラックスネーク」からの土地。

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

シアーズとKマートはイヴァンカ・トランプの商品を自分たちで取り除いています

写真:APシアーズとKマートは、イヴァンカ・トランプのトランプホームアイテムのコレクションも、誰も購入したくないために削除しました。シアーズとKマートの両方の親会社であるシアーズホールディングスは、土曜日のABCニュースへの声明で、彼らが気にかけていると辛抱強く説明しましたトランプラインを売り続けるにはお金を稼ぐことについてあまりにも多く。

ポテトチップスでたった10分でスペインのトルティーヤを作る

ポテトチップスでたった10分でスペインのトルティーヤを作る

伝統的なスペインのトルティーヤは通常、オリーブオイルで柔らかくなるまで調理されたポテトから始まります(30分以上かかる場合があります)が、ケトルで調理されたポテトチップスの助けを借りてわずか10分でテーブルに置くことができます。上のビデオはすべてがバラバラにならないように裏返す方法を含め、レシピ全体を説明しますが、必要なのは4〜5個の卵と3カップのケトルチップスだけです。

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、彼らが子供たちと行っているスパイをテーマにした活動を共有しています

ケイト・ミドルトンとウィリアム王子は、子供向けのパズルの本の序文を書き、ジョージ王子、シャーロット王女、ルイ王子と一緒にテキストを読むと述べた.

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

事故で押しつぶされたスイカは、動物を喜ばせ水分補給するために野生生物保護団体に寄付されました

Yak's Produce は、数十個のつぶれたメロンを野生動物のリハビリ専門家であるレスリー グリーンと彼女のルイジアナ州の救助施設で暮らす 42 匹の動物に寄付しました。

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

デミ・ロヴァートは、新しいミュージシャンのボーイフレンドと「幸せで健康的な関係」にあります: ソース

8 枚目のスタジオ アルバムのリリースに向けて準備を進めているデミ ロヴァートは、「スーパー グレート ガイ」と付き合っている、と情報筋は PEOPLE に確認しています。

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

Plathville の Kim と Olivia Plath が数年ぶりに言葉を交わすことへようこそ

イーサン プラスの誕生日のお祝いは、TLC のウェルカム トゥ プラスビルのシーズン 4 のフィナーレで、戦争中の母親のキム プラスと妻のオリビア プラスを結びつけました。

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

仕事の生産性を高める 8 つのシンプルなホーム オフィスのセットアップのアイデア

ホームオフィスのセットアップ術を極めよう!AppExert の開発者は、家族全員が一緒にいる場合でも、在宅勤務の技術を習得しています。祖父や曽祖父が共同家族で暮らしていた頃の記憶がよみがえりました。

2022 年、私たちのデジタル ライフはどこで終わり、「リアル ライフ」はどこから始まるのでしょうか?

20 年前のタイムトラベラーでさえ、日常生活におけるデジタルおよびインターネットベースのサービスの重要性に驚くことでしょう。MySpace、eBay、Napster などのプラットフォームは、高速化に焦点を合わせた世界がどのようなものになるかを示してくれました。

ニューロマーケティングの秘密科学

ニューロマーケティングの秘密科学

マーケティング担当者が人間の欲望を操作するために使用する、最先端の (気味が悪いと言う人もいます) メソッドを探ります。カートをいっぱいにして 3 桁の領収書を持って店を出る前に、ほんの数点の商品を買いに行ったことはありませんか? あなたは一人じゃない。

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

地理情報システムの日: GIS 開発者として学ぶべき最高の技術スタック

私たちが住んでいる世界を確実に理解するには、データが必要です。ただし、空間参照がない場合、このデータは地理的コンテキストがないと役に立たなくなる可能性があります。

Language