IT story

모범 사례 다국어 웹 사이트

hot-time 2020. 5. 29. 23:45
반응형

모범 사례 다국어 웹 사이트


나는 지금 몇 달 동안이 질문에 어려움을 겪었지만 지금까지 가능한 모든 옵션을 탐색해야 할 상황에 처하지 않았습니다. 지금은 가능성을 알고 다가오는 프로젝트에 사용할 개인적 취향을 만들 때가 된 것 같습니다.

내가 찾고있는 상황을 먼저 스케치하겠습니다

꽤 오랫동안 사용해온 콘텐츠 관리 시스템을 업그레이드 / 재개발하려고합니다. 그러나, 나는 다국어가이 시스템에서 크게 개선되었다고 생각합니다. 프레임 워크를 사용하지 않고 다음 프로젝트에 Laraval4를 사용하려고합니다. 라 라벨은 PHP를 더 깔끔하게 코딩하는 최선의 선택 인 것 같습니다. Sidenote: Laraval4 should be no factor in your answer. 플랫폼 / 프레임 워크에 독립적 인 일반적인 번역 방법을 찾고 있습니다.

번역해야 할 것

내가 찾고있는 시스템은 가능한 한 사용자 친화적이어야하며 번역 관리 방법은 CMS 내부에 있어야합니다. 번역 파일이나 html / php 파싱 된 템플릿을 수정하기 위해 FTP 연결을 시작할 필요가 없습니다.

또한 추가 테이블을 만들 필요없이 여러 데이터베이스 테이블을 변환하는 가장 쉬운 방법을 찾고 있습니다.

내가 뭘 생각 해냈어

내가 검색하고, 읽고, 이미 직접 시도한 것처럼. 내가 가지고있는 몇 가지 옵션이 있습니다. 그러나 나는 여전히 내가 정말로 추구하는 것에 대한 모범 사례 방법에 도달 한 것처럼 느끼지 않습니다. 지금, 이것은 내가 생각해 낸 방법이지만이 방법에도 부작용이 있습니다.

  1. PHP 구문 분석 템플릿 : PHP 가 템플릿 시스템을 구문 분석해야합니다. 이렇게하면 템플릿을 열거 나 수정하지 않고도 번역 된 매개 변수를 HTML에 삽입 할 수 있습니다. 게다가 PHP 구문 분석 템플릿을 사용하면 각 언어마다 하위 폴더를 두지 않고 전체 웹 사이트에 대해 하나의 템플릿을 가질 수 있습니다. 이 대상에 도달하는 방법은 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. 데이터베이스 테이블 번역 : 실제로 이것은 내가 가장 걱정하는 것입니다. News / Pages / etc의 번역을 만드는 방법을 찾고 있습니다. 가능한 빨리. 각 모듈에 대해 두 개의 테이블 (예 : NewsNews_translations)을 갖는 것이 옵션이지만 좋은 시스템을 얻으려면 많은 노력이 필요합니다. 나는 기반으로 해낸 것 중 하나 data versioning내가 쓴 시스템 : 하나의 데이터베이스 테이블 이름이 Translations이 테이블의 고유 한 조합을 가지고 language, tablename그리고primarykey. 예 : en_En / News / 1 (ID = 1 인 영어 버전의 뉴스 항목 참조). 그러나이 방법에는 두 가지 큰 단점이 있습니다. 먼저이 테이블은 데이터베이스에 많은 데이터가 있으면 꽤 길어지는 경향이 있으며 두 번째 로이 설정을 사용하여 테이블을 검색하는 것은 쉽지 않습니다. 예를 들어 항목의 SEO 슬러그를 검색하는 것은 전체 텍스트 검색 일 것입니다. 그러나 반면에 : 모든 테이블에서 번역 가능한 내용을 매우 빠르게 만들 수있는 빠른 방법이지만이 전문가가 단점을 과체중이라고는 생각하지 않습니다.
  5. 프론트 엔드 작업 : 또한 프론트 엔드에는 약간의 사고가 필요합니다. 물론 우리는 사용 가능한 언어를 데이터베이스에 저장하고 필요한 언어를 비활성화합니다. 이 방법으로 스크립트는 언어를 선택하기위한 드롭 다운을 생성 할 수 있으며 백엔드는 CMS를 사용하여 번역 할 수있는 내용을 자동으로 결정할 수 있습니다. 그런 다음 선택한 언어 (예 : en_EN)는 언어 파일을 볼 때 또는 웹 사이트의 컨텐트 항목에 대한 올바른 번역을 얻을 때 사용됩니다.

그래서 거기에 있습니다. 지금까지 내 아이디어. 날짜 등의 현지화 옵션도 포함하지 않았지만 서버가 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은 동일한 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이 언어 식별을 할 필요가 없기 때문에 내 눈에 ...이, 가장 좋은 솔루션입니다.

그리고 세 번째 옵션은 두 가지 옵션의 조합입니다 http://www.domain.com/about-us. 기본 언어에 "language-identification-less"-URL ( ) 사용. 하위 언어에 대해 번역 된 SEO 슬러그가 포함 된 URL을 사용합니다. http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

내 질문이 당신의 머리를 깨뜨리기를 바랍니다. 여기에서 이미 문제를 해결하는 데 도움이되었습니다. 이전에 사용했던 방법과 곧 출시 될 CMS에 대한 아이디어를 검토 할 수있는 가능성을 제공하십시오.

이 텍스트를 읽을 시간을내어 주셔서 감사합니다.

// Edit #1:

__ () 함수는 주어진 문자열을 번역하는 별칭입니다. 이 방법에는 아직 사용 가능한 번역이 없을 때 기본 텍스트가로드되는 일종의 대체 방법이 있어야합니다. 번역이없는 경우 번역을 삽입하거나 번역 파일을 재생성해야합니다.


주제의 전제

다국어 사이트에는 세 가지 특징이 있습니다.

  • 인터페이스 번역
  • 함유량
  • URL 라우팅

모두 서로 다른 방식으로 서로 연결되어 있지만 CMS 관점에서 다른 UI 요소를 사용하여 관리되고 다르게 저장됩니다. 처음 두 가지에 대한 구현과 이해에 확신이있는 것 같습니다. 문제는 후자의 측면에 관한 것이 었습니다- "URL 번역? 우리는 이것을해야합니까? 그렇지 않으면 어떻게합니까?"

URL은 무엇으로 만들 수 있습니까?

매우 중요한 것은 IDN으로 화려하지 마십시오 . 대신 음역을 선호하십시오 (또한 전사 및 로마자 표기). 언뜻보기에 IDN은 국제 URL에 대해 실용적인 옵션으로 보이지만 실제로 두 가지 이유로 광고 된대로 작동하지 않습니다.

  • 일부 브라우저는 같은 비 ASCII 문자를 켜집니다 'ч'또는 'ž''%D1%87''%C5%BE'
  • 사용자가 사용자 정의 테마를 사용하는 경우 테마의 글꼴에 해당 문자에 대한 기호가 없을 가능성이 높습니다

나는 실제로 몇 년 전에 Yii 기반 프로젝트 (끔찍한 프레임 워크, IMHO)에서 IDN 접근을 시도했습니다. 해당 솔루션을 폐기하기 전에 위에서 언급 한 두 가지 문제가 발생했습니다. 또한 공격 벡터 일 수 있습니다.

사용 가능한 옵션 ... 내가 보는 것처럼.

기본적으로 다음과 같이 두 가지 선택이 있습니다.

  • http://site.tld/[:query]: [:query]언어 및 컨텐츠 선택을 결정합니다.

  • http://site.tld/[:language]/[:query]: [:language]URL의 일부는 언어 선택을 정의 [:query]하며 내용을 식별하는 데만 사용됩니다.

쿼리는 A 및 Ω입니다.

당신이 선택할 수 http://site.tld/[:query]있습니다.

이 경우 언어의 기본 소스는 [:query]세그먼트 의 내용입니다 . 그리고 두 가지 추가 소스 :

  • $_COOKIE['lang']특정 브라우저의 가치
  • HTTP Accept-Language (1) , (2) 헤더 의 언어 목록

먼저 쿼리를 정의 된 라우팅 패턴 중 하나와 일치시켜야합니다 (선택이 라 라벨 인 경우 여기읽으십시오 ). 패턴이 성공적으로 일치하면 언어를 찾아야합니다.

패턴의 모든 세그먼트를 거쳐야합니다. 모든 해당 세그먼트에 대한 잠재적 번역을 찾고 사용 된 언어를 결정하십시오. 두 가지 추가 소스 (쿠키 및 헤더)는 라우팅 충돌이 발생하는 경우 ( "if"아님) 발생하는 데 사용됩니다.

예를 들어 보자 http://site.tld/blog/novinka.

"блог, новинка"영어로 약을 의미하는 음역입니다 "blog", "latest".

이미 알 수 있듯이 러시아어로 "блог"는 "blog"로 음역됩니다. 이는 첫 번째 부분 [:query]( 가장 좋은 경우 시나리오 ) ['en', 'ru']에서 가능한 언어 목록으로 끝나는 것을 의미 합니다. 그런 다음 "novinka"라는 다음 세그먼트를 사용하십시오. 가능성 목록에 하나의 언어 만있을 수 있습니다 ['ru'].

목록에 하나의 항목이 있으면 언어를 성공적으로 찾은 것입니다.

그러나 경우에 따라 2 (예 : 러시아어 및 우크라이나어) 이상의 가능성 .. 또는 0 가능성으로 끝나는 경우가 있습니다. 올바른 옵션을 찾으려면 쿠키 및 / 또는 헤더를 사용해야합니다.

그리고 다른 모든 방법이 실패하면 사이트의 기본 언어를 선택합니다.

매개 변수로서의 언어

대안은로 정의 할 수있는 URL을 사용하는 것 http://site.tld/[:language]/[:query]입니다. 이 경우 쿼리를 번역 할 때 언어를 추측 할 필요가 없습니다. 그 시점에서 사용할 언어를 이미 알고 있기 때문입니다.

쿠키 값이라는 2 차 언어 소스도 있습니다. 그러나 "콜드 스타트"(사용자가 처음으로 사이트를 사용자 정의 쿼리로 열 때)의 경우 알 수없는 언어를 처리하지 않기 때문에 Accept-Language 헤더를 망칠 필요가 없습니다.

대신 우선 순위가 지정된 3 가지 간단한 옵션이 있습니다.

  1. [:language]세그먼트가 설정되어 있으면 사용하십시오
  2. $_COOKIE['lang']설정되어 있으면 사용하십시오
  3. 기본 언어를 사용하십시오

언어가있는 경우 간단히 쿼리를 번역하려고 시도하고 변환에 실패하면 해당 세그먼트에 대한 "기본값"(라우팅 결과에 따라)을 사용하십시오.

여기에 세 번째 옵션이 아닌가요?

예, 기술적으로는 두 가지 접근 방식을 결합 할 수 있습니다,하지만 그 과정을 복잡하게 만의 수동 변경 URL을 원하는 사람들을 수용 할 http://site.tld/en/news로를 http://site.tld/de/news독일에 변화에 대한 뉴스 페이지를 기대합니다.

그러나이 경우조차도 쿠키 값 (이전의 언어 선택에 대한 정보가 포함되어 있음)을 사용하여 마술과 희망을 덜 사용하여 완화 할 수 있습니다.

어떤 접근법을 사용해야합니까?

이미 짐작 하셨겠지만 http://site.tld/[:language]/[:query]보다 합리적인 옵션으로 추천 합니다.

또한 실제 상황에서는 URL에 "title"이라는 세 번째 주요 부분이 있습니다. 온라인 상점의 제품 이름 또는 뉴스 사이트의 기사 제목과 같습니다.

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

이 경우 '/news/article/121415'쿼리이고 'EU-as-global-reserve-currency'제목은입니다. SEO 목적으로 만 사용하십시오.

라 라벨에서 할 수 있습니까?

기본적으로는 아닙니다.

나는 그것에 익숙하지 않지만 Laravel은 내가 본 것으로부터 간단한 패턴 기반 라우팅 메커니즘을 사용합니다. 다국어 URL을 구현하려면 다국어 라우팅이 다른 형식의 저장소 (데이터베이스, 캐시 및 / 또는 구성 파일)에 액세스해야하므로 아마도 핵심 클래스확장 해야 할 것입니다 .

라우팅되었습니다. 지금 무엇?

결과적으로 두 가지 귀중한 정보, 즉 현재 언어와 번역 된 쿼리 세그먼트가 생깁니다. 그런 다음이 값을 사용하여 클래스에 디스패치하여 결과를 생성 할 수 있습니다.

기본적으로 다음 URL : http://site.tld/ru/blog/novinka(또는없는 버전 '/ru')은 다음과 같이 변경됩니다.

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

파견에 사용하는 것 :

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

.. 또는 특정 구현에 따라 일부 변형.


Thomas Bley가 제안한 전처리기를 사용하여 성능 저하없이 i18n 구현

직장에서 우리는 최근 몇 가지 속성에 i18n을 구현했으며, 우리가 어려움을 겪고있는 것 중 하나는 즉석 번역을 처리 할 때의 성능 저하였습니다. 그런 다음 Thomas Bley 의이 위대한 블로그 게시물을 발견 했습니다. 이는 최소한의 성능 문제로 i18n을 사용하여 많은 트래픽을 처리하는 방식에 영감을주었습니다.

PHP에서 알 수 있듯이 모든 번역 작업에 대해 함수를 호출하는 대신 자리 표시 자와 함께 기본 파일을 정의한 다음 전처리기를 사용하여 해당 파일을 캐시합니다 (파일 수정 시간을 저장하여 서비스를 제공하고 있는지 확인) 항상 최신 콘텐츠).

번역 태그

Thomas는 {tr}{/tr}태그를 사용하여 번역이 시작되고 끝나는 위치를 정의합니다. 때문에 우리가 나뭇 가지를 사용하고 있다는 사실에, 우리는 사용하지 않으려는 {우리가 사용하는, 그래서 혼동을 피하기 위해 [%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();

쿠키 (또는 쿠키를 얻을 수없는 경우 세션 변수)에 언어를 저장 한 다음 모든 요청에서 언어를 검색합니다. $_GET언어를 재정의하기 위해이 매개 변수를 선택적 매개 변수 와 결합 할 수 있지만, 언어별로 하위 도메인 또는 언어 당 페이지를 제안하지 않으므로 인기있는 페이지를 확인하기가 어렵고 인바운드 값이 줄어 듭니다. 더 드물게 퍼지게하는 링크.

왜이 방법을 사용합니까?

우리는 세 가지 이유로이 전처리 방법을 좋아합니다.

  1. 거의 변경되지 않는 컨텐츠에 대해 여러 함수를 호출하지 않으면 서 엄청난 성능 향상이 가능합니다 (이 시스템을 사용하면 프랑스어로 100 만 명의 방문자가 번역 대체를 한 번만 실행하게됩니다).
  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 슬러그 번역

여기서 두 가지를 결합했습니다. 하나는 부트 스트랩의 함수이며 $_GET언어에 대한 매개 변수를 허용 하고 쿠키 변수를 재정의하고 다른 하나는 여러 슬러그를 허용하는 라우팅입니다. 그런 다음 라우팅에서 다음과 같이 할 수 있습니다.

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

이러한 파일은 플랫 파일에 저장되어 관리자 패널에서 쉽게 쓸 수 있습니다. JSON 또는 XML은이를 지원하기위한 좋은 구조를 제공 할 수 있습니다.

몇 가지 다른 옵션에 관한 참고 사항

PHP 기반 실시간 번역

나는 이것이 전처리 번역에 비해 어떤 이점을 제공한다는 것을 알 수 없습니다.

프론트 엔드 기반 번역

나는 이것들이 오랫동안 흥미로운 것을 발견했지만 몇 가지주의 사항이 있습니다. 예를 들어, 번역하려는 웹 사이트의 전체 구문 목록을 사용자에게 제공해야합니다. 숨겨져 있거나 액세스 할 수없는 사이트 영역이있는 경우 문제가 될 수 있습니다.

또한 모든 사용자가 귀하의 사이트에서 Javascript를 기꺼이 사용할 수 있다고 가정해야하지만, 통계에 따르면 사용자의 약 2.5 %가이를 사용하지 않고 실행되고 있습니다 (또는 Noscript를 사용하여 사이트 사용을 차단) .

데이터베이스 기반 번역

PHP의 데이터베이스 연결 속도는 집에 쓸만한 것이 아니며, 이는 번역 할 모든 구에서 함수를 호출하는 이미 높은 오버 헤드를 추가합니다. 이 접근 방식에서는 성능 및 확장 성 문제가 압도적입니다.


휠을 발명하지 말고 gettext 및 ISO 언어 abbrevs 목록을 사용하지 않는 것이 좋습니다. 널리 사용되는 CMS 또는 프레임 워크에서 i18n / l10n이 어떻게 구현되는지 보셨습니까?

gettext를 사용하면 많은 경우가 이미 여러 형태의 숫자처럼 구현되는 강력한 도구를 갖게됩니다. 영어에서는 단수와 복수의 두 가지 옵션 만 있습니다. 그러나 러시아어에는 예를 들어 3 가지 형식이 있으며 영어만큼 간단하지 않습니다.

또한 많은 번역가들은 이미 gettext 작업 경험이 있습니다.

CakePHP 또는 Drupal을 살펴보십시오 . 다국어 지원. 인터페이스 현지화의 예로서 CakePHP 및 컨텐츠 번역의 예로서 Drupal.

l10n의 경우 데이터베이스를 사용하는 것은 전혀 아닙니다. 쿼리에 톤이 될 것입니다. 표준 접근 방식은 모든 l10n 데이터를 초기 단계 (또는 지연로드를 선호하는 경우 i10n 기능을 처음 호출하는 동안)로 메모리에 가져 오는 것입니다. .po 파일 또는 DB에서 모든 데이터를 한 번에 읽을 수 있습니다. 그리고 배열에서 요청 된 문자열을 읽는 것보다.

인터페이스를 번역하기 위해 온라인 툴을 구현해야하는 경우 DB에 모든 데이터를 보유 할 수 있지만 모든 데이터를 파일로 저장하여 작업 할 수 있습니다. 메모리의 데이터 양을 줄이려면 번역 된 모든 메시지 / 문자열을 그룹으로 나누고 가능한 경우 필요한 그룹 만로드 할 수 있습니다.

그래서 당신은 # 3에서 완전히 옳습니다. 한 가지 예외가 있습니다. 일반적으로 컨트롤러 당 파일이 아닌 하나의 큰 파일입니다. 하나의 파일을 여는 것이 성능상 가장 좋기 때문입니다. include / require가 호출 될 때 파일 조작을 피하기 위해 일부로드 된 웹 애플리케이션이 모든 PHP 코드를 하나의 파일로 컴파일한다는 것을 알고있을 것입니다.

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 스키마와 번역을위한 충분한 인터페이스가 있습니다. 기사를 작성하고 해당 언어를 선택하는 것처럼. 나중에 나중에 다른 언어로 번역 할 수 있습니다.

드루팔 번역 인터페이스

URL 슬러그에는 문제가 없다고 생각합니다. 슬러그에 대해 별도의 테이블을 만들면 올바른 결정이 될 것입니다. 또한 올바른 인덱스를 사용하면 대량의 데이터가 있어도 테이블을 쿼리하는 데 문제가 없습니다. 그리고 전체 텍스트 검색은 아니지만 슬러그에 varchar 데이터 유형을 사용하고 해당 필드에 색인을 가질 수 있으면 문자열이 일치합니다.

PS 죄송합니다. 제 영어는 완벽하지 않습니다.


웹 사이트의 콘텐츠 양에 따라 다릅니다. 처음에는 다른 사람들과 마찬가지로 데이터베이스를 사용했지만 데이터베이스의 모든 작업을 스크립팅하는 데 시간이 오래 걸릴 수 있습니다. 나는 이것이 이상적인 방법이라고 말하지 않으며, 특히 많은 텍스트가있는 경우 데이터베이스를 사용하지 않고 빨리하고 싶다면이 방법이 효과적 일 수 있지만 사용자가 데이터를 입력 할 수는 없습니다. 번역 파일로 사용됩니다. 그러나 번역을 직접 추가하면 작동합니다.

이 텍스트가 있다고 가정 해 봅시다.

Welcome!

번역이있는 데이터베이스에 이것을 입력 할 수 있지만 다음과 같이 할 수도 있습니다.

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

이제 웹 사이트에서 쿠키를 사용하는 경우 예를 들어 다음과 같습니다.

$_COOKIE['language'];

쉽게 사용할 수 있도록 쉽게 사용할 수있는 코드로 변환 해 보겠습니다.

$language=$_COOKIE['language'];

쿠키 언어가 웨일스 어이고 다음 코드가있는 경우 :

echo $welcome[$language];

이것의 결과는 다음과 같습니다.

Croeso!

웹 사이트에 많은 번역을 추가해야하고 데이터베이스가 너무 많이 소비되는 경우 배열을 사용하는 것이 이상적인 솔루션이 될 수 있습니다.


번역을 위해 데이터베이스에 실제로 의존하지 말 것을 제안합니다. 매우 지저분한 작업 일 수 있으며 데이터 인코딩의 경우 극단적 인 문제가 될 수 있습니다.

나는 전에 비슷한 문제에 직면했고 내 문제를 해결하기 위해 다음 수업을 썼다.

개체 : 로캘 \ 로캘

<?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}로 대체 됨 메소드에 전달 된 두 번째 인수로 대체 됨Locale::translate('key_name','arg1','arg2')

탐지 작동 방식

  • 기본적 geoip으로 설치되어 있으면 국가 코드를 반환 geoip_country_code_by_name하고 지오 IP가 설치되지 않은 경우 대체 HTTP_ACCEPT_LANGUAGE헤더

단지 하위 답변 : 언어 식별자가있는 번역 된 URL을 절대적으로 사용 하십시오 : http://www.domain.com/nl/over-ons
하이브리드 솔루션은 복잡 해지는 경향이 있으므로 그대로 사용하십시오. 왜? URL은 SEO에 필수적입니다.

DB 변환 정보 : 언어 수는 다소 고정되어 있습니까? 아니면 예측할 수없고 역동적입니까? 고정되어 있으면 새 열을 추가하고 그렇지 않으면 여러 테이블로 이동합니다.

그러나 일반적으로 Drupal을 사용하지 않는 이유는 무엇입니까? 나는 모두가 자신의 CMS를 만들고 싶어한다는 것을 알고 있습니다.


나는 Symfony 프레임 워크를 사용하기 전에 얼마 전에 같은 프로브를 가지고있었습니다 .

  1. arameters pageId (또는 objectId, # 2에 설명 된 objectTable), 대상 언어 및 대체 (기본) 언어의 선택적 매개 변수가있는 __ () 함수를 사용하십시오. 기본 언어는 일부 전역 구성에서 나중에 쉽게 변경할 수 있도록 설정할 수 있습니다.

  2. 데이터베이스에 내용을 저장하기 위해 (pageId, language, content, variables) 구조를 사용했습니다.

    • pageId는 번역하려는 페이지의 FK입니다. 뉴스, 갤러리 등과 같은 다른 개체가있는 경우 objectId, objectTable이라는 두 개의 필드로 분리하면됩니다.

    • 언어-분명히 ISO 언어 문자열 EN_en, LT_lt, EN_us 등을 저장합니다.

    • content-변수 대체를 위해 와일드 카드와 함께 번역하려는 텍스트. 예 "Hello mr. %% name %%. 귀하의 계정 잔고는 %% balance %%입니다."

    • variables-json 인코딩 변수 PHP는이를 빠르게 구문 분석하는 기능을 제공합니다. 예 : "이름 : Laurynas, 잔액 : 15.23".

    • 슬러그 필드도 언급했습니다. 빠른 검색 방법을 위해이 테이블에 자유롭게 추가 할 수 있습니다.

  3. 번역을 캐싱 할 때 데이터베이스 호출을 최소로 줄여야합니다. PHP 언어에서 가장 빠른 구조이므로 PHP 배열에 저장해야합니다. 이 캐싱을 만드는 방법은 귀하에게 달려 있습니다. 내 경험상 지원되는 각 언어에 대한 폴더와 각 pageId에 대한 배열이 있어야합니다. 번역을 업데이트 한 후 캐시를 다시 작성해야합니다. 변경된 어레이 만 재생성해야합니다.

  4. 나는 # 2에 그 대답을 생각

  5. 당신의 아이디어는 완벽하게 논리적입니다. 이것은 매우 간단하며 문제가되지 않을 것이라고 생각합니다.

번역 테이블에 저장된 슬러그를 사용하여 URL을 번역해야합니다.

마지막 단어

항상 모범 사례를 연구하는 것이 좋지만 바퀴를 재발 명하지는 마십시오. 잘 알려진 프레임 워크에서 컴포넌트를 가져 와서 사용하십시오.

Symfony translation component를 살펴보십시오 . 좋은 코드 기반이 될 수 있습니다.


나는 이미 주어진 답을 다듬 으려고하지 않을 것이다. 대신 내 OOP PHP 프레임 워크가 번역을 처리하는 방법에 대해 설명하겠습니다.

내부적으로 내 프레임 워크는 en, fr, es, cn 등과 같은 코드를 사용합니다. 배열은 웹 사이트에서 지원하는 언어를 보유합니다. array ( 'en', 'fr', 'es', 'cn') 언어 코드는 $ _GET (lang = fr)을 통해 전달되며 전달되지 않거나 유효하지 않은 경우 배열의 첫 번째 언어로 설정됩니다. 따라서 프로그램 실행 중과 처음부터 언제든지 현재 언어가 알려져 있습니다.

일반적인 응용 프로그램에서 번역해야하는 콘텐츠의 종류를 이해하면 유용합니다.

1) 클래스 (또는 절차 코드)의 오류 메시지 2) 클래스 (또는 절차 코드)의 오류가 아닌 메시지 3) 페이지 내용 (보통 데이터베이스에 저장) 4) 사이트 전체 문자열 (예 : 웹 사이트 이름) 5) 스크립트- 특정 문자열

첫 번째 유형은 이해하기 쉽습니다. 기본적으로 "데이터베이스에 연결할 수 없습니다 ..."와 같은 메시지에 대해 이야기하고 있습니다. 이러한 메시지는 오류가 발생할 때만로드해야합니다. 내 관리자 클래스는 다른 클래스로부터 전화를 받고 매개 변수로 전달 된 정보를 사용하여 단순히 해당 클래스 폴더로 이동하여 오류 파일을 검색합니다.

두 번째 유형의 오류 메시지는 양식의 유효성 검사가 잘못되었을 때 표시되는 메시지와 유사합니다. ( "비어 둘 수 없습니다"또는 "5 자 이상의 암호를 선택하십시오"). 클래스가 실행되기 전에 문자열을로드해야합니다.

실제 페이지 내용의 경우 언어 당 하나의 테이블을 사용하며 각 테이블에는 언어 코드가 붙어 있습니다. 따라서 en_content는 영어 컨텐츠가 포함 된 테이블이고 es_content는 스페인, cn_content는 중국, fr_content는 프랑스어입니다.

네 번째 문자열은 웹 사이트 전체와 관련이 있습니다. 언어 코드를 사용하여 명명 된 구성 파일 (en_lang.php, es_lang.php 등)을 통해로드됩니다. 글로벌 언어 파일에서 영어 글로벌 파일에 array ( 'English', 'Chinese', 'Spanish', 'French')와 같은 번역 된 언어를로드하고 array ( 'Anglais', 'Chinois', ' 프랑스어 파일에서 Espagnol ','Francais '). 따라서 언어 선택에 대한 드롭 다운을 채우면 올바른 언어입니다.)

마지막으로 스크립트 별 문자열이 있습니다. 따라서 요리 응용 프로그램을 작성하면 "오븐이 충분히 뜨겁지 않은 것"일 수 있습니다.

내 응용 프로그램 사이클에서 전역 언어 파일이 먼저로드됩니다. 여기에는 "Jack 's Website"와 같은 전역 문자열뿐만 아니라 일부 클래스에 대한 설정도 있습니다. 기본적으로 언어 나 문화에 의존하는 모든 것. 거기에있는 일부 문자열에는 날짜 마스크 (MMDDYYYY 또는 DDMMYYYY) 또는 ISO 언어 코드가 포함되어 있습니다. 기본 언어 파일에는 개별 클래스에 대한 문자열이 포함되어 있기 때문에 개별 클래스에 대한 문자열을 포함합니다.

디스크에서 읽은 두 번째 및 마지막 언어 파일은 스크립트 언어 파일입니다. lang_en_home_welcome.php는 home / welcome 스크립트의 언어 파일입니다. 스크립트는 모드 (홈)와 작업 (환영)에 의해 정의됩니다. 각 스크립트에는 구성 및 lang 파일이있는 자체 폴더가 있습니다.

스크립트는 위에서 설명한대로 컨텐츠 테이블의 이름을 데이터베이스에서 컨텐츠를 가져옵니다.

문제가 발생하면 관리자는 언어 별 오류 파일을 가져올 위치를 알고 있습니다. 해당 파일은 오류가 발생한 경우에만로드됩니다.

결론은 분명합니다. 응용 프로그램 또는 프레임 워크 개발을 시작하기 전에 번역 문제를 고려하십시오. 번역을 통합하는 개발 워크 플로도 필요합니다. 내 프레임 워크를 사용하여 전체 사이트를 영어로 개발 한 다음 모든 관련 파일을 번역합니다.

번역 문자열이 구현되는 방식에 대한 간단한 최종 단어. 내 프레임 워크에는 다른 모든 서비스에서 사용할 수있는 서비스를 실행하는 단일 글로벌 $ manager가 있습니다. 예를 들어 양식 서비스는 html 서비스를 보유하고이를 사용하여 html을 작성합니다. 내 시스템의 서비스 중 하나는 번역기 서비스입니다. $ translator-> set ($ service, $ code, $ string)은 현재 언어의 문자열을 설정합니다. 언어 파일은 그러한 문장의 목록입니다. $ translator-> get ($ service, $ code)는 번역 문자열을 검색합니다. $ code는 1과 같은 숫자이거나 'no_connection'과 같은 문자열 일 수 있습니다. 서비스마다 번역기의 데이터 영역에 고유 한 네임 스페이스가 있으므로 서비스간에 충돌이 없습니다.

나는 몇 년 전에해야했던 것처럼 누군가가 바퀴를 재발 명하는 작업을 구할 수 있기를 바랍니다.


나는 나 자신과 관련된 질문을 계속해서 반복하면서 공식 언어로 길을 잃었습니다 ...하지만 약간의 도움을 드리기 위해 약간의 결과를 공유하고 싶습니다.

고급 CMS를 살펴 보는 것이 좋습니다

Typo3위해 PHP(내가 거기 물건을 많이는하지만 대부분의 성숙 생각 사람 이잖아 알고)

PlonePython

2013 년 웹이 다르게 작동한다는 것을 알게되면 처음부터 시작하십시오. 이는 숙련 된 숙련 된 직원들로 구성된 팀을 구성하여 새로운 CMS를 구축하는 것을 의미합니다. 그 목적에 맞는 폴리머를보고 싶을 수도 있습니다.

코딩 및 다국어 웹 사이트 / 모국어 지원과 관련하여 모든 프로그래머는 유니 코드에 대한 단서가 있어야한다고 생각합니다. 유니 코드를 모른다면 가장 확실하게 데이터를 망칠 것입니다. 수천 가지 ISO 코드를 사용하지 마십시오. 그들은 당신에게 약간의 기억을 절약 할 것입니다. 그러나 UTF-8로 문자 그대로 모든 것을 중국어 문자를 저장할 수 있습니다. 그러나이를 위해서는 기본적으로 utf-16 또는 utf-32로 만드는 2 또는 4 바이트 문자를 저장해야합니다.

URL 인코딩과 관련하여 다시 인코딩을 혼합해서는 안되며 최소한 도메인 이름에 대해 브라우저와 같은 응용 프로그램을 제공하는 다른 로비에 의해 정의 된 규칙이 있음을 알고 있어야합니다. 예를 들어 도메인은 다음과 매우 유사 할 수 있습니다.

ьankofamerica.com 또는 bankofamerica.com samesamebutdifferent;)

물론 모든 인코딩 작업을하려면 파일 시스템이 필요합니다. utf-8 파일 시스템을 사용하는 유니 코드의 또 다른 장점.

번역에 관한 것이면 문서의 구조에 대해 생각하십시오. 예를 들어 책이나 기사. docbook이러한 구조에 대해 이해하기 위한 사양이 있습니다. 그러나 HTML에서는 컨텐츠 블록에 관한 것입니다. 따라서 해당 수준, 웹 페이지 수준 또는 도메인 수준에서 번역을 원합니다. 따라서 블록이 존재하지 않는 경우 웹 페이지가 존재하지 않으면 상위 탐색 수준으로 리디렉션됩니다. 탐색 구조에서 도메인이 완전히 달라야하는 경우 관리해야 할 완전히 다른 구조입니다. 이것은 이미 Typo3로 수행 할 수 있습니다.

내가 아는 가장 성숙한 프레임 워크에 대해 MVC와 같은 일반적인 일을하려면 (실제로 미워하는 단어! "성능"과 같이) 무언가를 팔고 싶다면 performance와 featurerich라는 단어를 사용하고 ... 지옥)입니다 Zend. PHP 혼돈 코더에 표준을 적용하는 것이 좋은 것으로 입증되었습니다. 그러나 typo3에는 CMS 외에 프레임 워크가 있습니다. 최근에 재개발되어 현재 flow3이라고합니다. 물론 프레임 워크는 데이터베이스 추상화, 템플릿 및 캐싱 개념을 다루지 만 개별적인 장점이 있습니다.

캐싱에 관한 것이라면 ... 그것은 굉장히 복잡하고 다층적일 수 있습니다. PHP에서는 accellerator, opcode뿐만 아니라 html, httpd, mysql, xml, css, js ... 모든 종류의 캐시에 대해서도 생각할 것입니다. 물론 일부 부분은 캐시되어야하며 블로그 답변과 같은 동적 부분은 그렇지 않아야합니다. 일부는 생성 된 URL로 AJAX를 통해 요청해야합니다. JSON, 해시 뱅

그런 다음 웹 사이트의 일부 구성 요소를 특정 사용자 만 액세스하거나 관리 할 수 있기를 원하므로 개념적으로 큰 역할을합니다.

또한 당신이하고 싶습니다 통계를 데이터베이스의 서로 다른 유형을 필요로하므로, 어쩌면 배포 한 시스템 / facebooks 등 소프트웨어의 페이스 북은 ... 당신의 이상 상단 CMS를 기반으로 구축되는 , bigdata, XML을 inmemory, 무엇이든지 .

글쎄, 지금은 충분하다고 생각합니다. typo3 / plone 또는 언급 된 프레임 워크에 대해 들어 본 적이 없다면 공부하기에 충분합니다. 그 길에는 아직 묻지 않은 질문에 대한 많은 솔루션이 있습니다.

그렇다면 2013 년과 PHP가 곧 죽을 것이기 때문에 새로운 CMS를 만들어 보도록하겠습니다.

행운을 빕니다!

그리고 btw. 사람들은 앞으로 더 이상 웹 사이트를 가지지 않을 것입니까? 우리는 모두 Google+에 있을까요? 개발자들이 좀 더 창의적이되고 유용한 일을하기를 바랍니다 (borgle에 동화되지 않기 위해)

//// 편집 /// 기존 응용 프로그램에 대한 약간의 생각 :

PHP mysql CMS가 있고 다중 언어 지원을 포함하려는 경우. 언어에 대한 추가 열이있는 테이블을 사용하거나 동일한 테이블에 객체 ID 및 언어 ID가있는 번역을 삽입하거나 언어에 대해 동일한 테이블을 생성하고 거기에 객체를 삽입 한 다음 원하는 경우 선택 유니온을 만들 수 있습니다 모두 표시되도록합니다. 데이터베이스의 경우 utf8 일반 ci를 사용하고 물론 프론트 / 백엔드에서 utf8 텍스트 / 인코딩을 사용하십시오. 이미 설명 된 방식으로 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을 "다시 작성"해야합니다.


데이터베이스 작업 :

언어 테이블 '언어'를 작성하십시오.

필드:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

데이터베이스 'content'에 테이블을 작성하십시오.

필드:

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/를 참조 하십시오 .


거의 모든 사이트가 프랑스어와 영어 인 퀘벡에 거주하는 사람으로서 ... WP에 대한 대부분의 다국어 플러그인은 아니지만 많은 것을 시도했습니다 ... 내 모든 사이트와 함께 작동하는 유일한 유용한 솔루션은 mQtranslate입니다 ... 나는 살고 그것으로 죽는다!

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


무슨 일 워드 프레스 + MULTI-LANGUAGE SITE BASIS(플러그인)? 사이트 구조는 다음과 같습니다.

  • example.com/ ENG / 카테고리 1 / ...
  • 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

그러나 플러그인이 여전히 활성화되어 있는지 확인하십시오.


Javascript를 업로드 할 수있는 모든 웹 사이트에서 작동하는 정말 간단한 옵션은 www.multilingualizer.com입니다.

모든 언어의 모든 텍스트를 한 페이지에 넣은 다음 사용자가 볼 필요가없는 언어를 숨 깁니다. 잘 작동합니다.

참고 URL : https://stackoverflow.com/questions/19249159/best-practice-multi-language-website

반응형