programing

다국어 베스트 프랙티스 웹사이트

kingscode 2022. 10. 1. 21:12
반응형

다국어 베스트 프랙티스 웹사이트

저는 이 문제에 대해 몇 달 동안 고민해 왔지만, 이전에는 모든 가능한 옵션을 모색할 필요가 있었던 상황은 아니었습니다.지금은 그 가능성을 알아보고 앞으로의 프로젝트에 사용할 나만의 취향을 만들어야 할 때라고 생각합니다.

먼저 제가 찾고 있는 상황을 스케치해 보겠습니다.

오랫동안 사용해 온 컨텐츠 관리 시스템을 업그레이드/재개발하려고 합니다.하지만, 저는 다국어가 이 시스템의 큰 발전이라고 생각합니다.이전에는 어떤 프레임워크도 사용하지 않았지만, 이번 프로젝트에서는 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, Larabel의 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.」입니다..ini 라고 합니다.이 폴더에는, 다음의 데이터가 포함되어 있을 필요가 있습니다.format parameter=value; 각 있을 해당 언어 파일이 될 수 방법이라고 합니다. 렌더링되는 각 뷰에는 고유한 언어 파일이 있을 경우 해당 언어 파일이 포함될 수 있기 때문에 이것이 가장 좋은 방법이라고 생각합니다.그런 다음 언어 매개 변수를 글로벌 범위가 아닌 특정 보기에 로드하여 매개 변수가 서로 덮어쓰지 않도록 해야 합니다.
  4. 데이터베이스 테이블 번역: 사실 이것이 가장 걱정되는 부분입니다.뉴스나 페이지 등의 번역을 가능한 한 빨리 작성할 수 있는 방법을 찾고 있습니다.각 모듈에 2개의 테이블이 있다(예:News그리고.News_translations)도 옵션입니다만, 좋은 시스템을 얻기 위해서 많은 노력을 하고 싶다고 생각하고 있습니다.제가 생각해낸 것 중 하나는data versioning작성한 시스템: 데이터베이스 테이블명은 1개입니다.Translations, 이 테이블에는, 다음의 일의의 편성이 있습니다.language,tablename그리고.primarykey. 예: en_En / News / 1 (ID=1의 뉴스 항목의 영어 버전 참조)그러나 이 방법에는 두 가지 큰 단점이 있습니다.첫째, 이 테이블은 데이터베이스에 데이터가 많으면 상당히 길어지는 경향이 있고, 둘째, 이 설정을 사용하여 테이블을 검색하는 것은 매우 어려운 일입니다.예를 들어 아이템의 SEO slug를 검색하는 것은 완전한 텍스트 검색으로 매우 바보 같은 일입니다.한편, 모든 테이블에서 번역 가능한 컨텐츠를 매우 빠르게 작성할 수 있지만, 이 프로가 단점을 과대평가한다고는 생각하지 않습니다.
  5. 프런트 엔드 작업: 프런트 엔드도 고려해야 합니다.물론 사용 가능한 언어를 데이터베이스에 저장하고 필요한 언어를 비활성화합니다.이렇게 하면 스크립트는 언어를 선택하는 드롭다운을 생성하고 백엔드는 CMS를 사용하여 수행할 수 있는 번역을 자동으로 결정할 수 있습니다.선택한 언어(예를 들어 en_EN)는 뷰용 언어 파일을 가져올 때 또는 웹 사이트의 콘텐츠 항목에 대한 올바른 번역을 얻을 때 사용됩니다.

자, 여기 있네요.지금까지의 내 생각.아직 날짜 등에 대한 현지화 옵션도 포함되어 있지 않습니다만, 제 서버가 PHP5.3.2+를 서포트하고 있기 때문에, http://devzone.zend.com/1500/internationalization-in-php-53/ 에서 설명한 대로 intl 확장자를 사용하는 것이 가장 좋은 옵션입니다.그러나, 이것은 향후의 개발에서 유용하게 쓰일 것입니다.현시점에서는, Web 사이트의 컨텐츠의 번역의 베스트 프랙티스를 어떻게 갖추느냐가 주된 과제입니다.

여기서 설명한 것 외에 아직 결정하지 못한 것이 하나 더 있습니다. 단순한 질문처럼 보이지만, 사실 그것은 저를 골치 아프게 하고 있습니다.

URL 변환이거 할까 말까? 어떤 방법으로?

다음 URL이 있는 경우:http://www.domain.com/about-us그리고 영어는 나의 기본 언어이다.이 URL을 다음 URL로 변환해야 합니까?http://www.domain.com/over-ons내 언어로 네덜란드어를 선택할 때?아니면 간단한 방법으로 다음 사이트에서 볼 수 있는 페이지의 내용을 변경할까요?/about. 마지막 옵션은 유효한 옵션이 아닌 것 같습니다.같은 URL의 여러 버전이 생성되기 때문에 이 콘텐츠 인덱싱은 올바른 방법으로 실패합니다.

또 다른 옵션은http://www.domain.com/nl/about-us대신.이렇게 하면 각 콘텐츠에 대해 적어도 하나의 URL이 생성됩니다.또한 이것은 예를 들어 다른 언어로 가는 것이 더 쉬울 것이다.http://www.domain.com/en/about-us또한 구글 방문자와 휴먼 방문자 모두 URL을 쉽게 이해할 수 있습니다.이 옵션을 사용하면 기본 언어를 어떻게 해야 합니까?기본적으로 선택된 언어를 기본 언어로 삭제하시겠습니까?리다이렉트 중http://www.domain.com/en/about-us로.http://www.domain.com/about-us...CMS가 하나의 언어에만 설정되어 있는 경우 URL에 이 언어 식별 정보를 포함할 필요가 없기 때문에 이것이 최선의 해결책이라고 생각합니다.

세 번째 옵션은 두 가지 옵션 중 하나를 조합한 것입니다.언어 식별이 필요 없는 URL 사용(http://www.domain.com/about-us)를 사용합니다.또한 하위 언어에는 변환된 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에 유효한 옵션이라고 생각되지만 실제로는 다음 두 가지 이유로 애드버타이즈된 것처럼 동작하지 않습니다.

  • 일부 브라우저는 비ASC를 전환합니다.II는 다음과 같습니다.'ч'또는'ž'안으로'%D1%87'그리고.'%C5%BE'
  • 사용자가 사용자 지정 테마를 가지고 있는 경우, 테마의 글꼴에는 해당 문자에 대한 기호가 없을 가능성이 높습니다.

저는 몇 년 전 Yii 기반의 프로젝트(끔찍한 프레임워크, IMHO)에서 IDN 어프로치를 시도했습니다.저는 그 해결책을 폐기하기 전에 위의 두 가지 문제를 모두 겪었습니다.그리고 공격 벡터일 수도 있다고 생각합니다.

사용 가능한 옵션... 제가 보기엔...

기본적으로 다음과 같이 추상화할 수 있는 두 가지 선택지가 있습니다.

  • http://site.tld/[:query]:어디바이스[:query]다국어 베스트 프랙티스 웹사이트언어와 콘텐츠의 양쪽 선택지를 결정하다

  • http://site.tld/[:language]/[:query]:어디바이스[:language]URL의 일부는 언어의 선택을 정의합니다.[:query]의 내용을 식별하기 위해서만 사용됩니다.

쿼리는 α 및 δ 입니다.

예를 들어, 당신이http://site.tld/[:query].

이 경우 주요 언어 소스가 하나 있습니다:[:query]세그먼트(segment) 및 2개의 추가 소스:

  • 가치$_COOKIE['lang']특정 브라우저에 대해
  • HTTP Accept-Language 헤더 언어 목록

먼저 정의된 라우팅 패턴 중 하나에 쿼리를 일치시켜야 합니다(선택사항이 Laravel일 경우 여기서 읽습니다).패턴이 올바르게 일치하면 언어를 찾아야 합니다.

패턴의 모든 세그먼트를 검토해야 합니다.이러한 세그먼트 모두에 대해 번역 가능성이 있는 것을 찾아, 사용된 언어를 특정합니다.2개의 추가 소스(cookie와 헤더)는 라우팅 경합이 발생했을 때('if'가 아닌) 이를 해결하기 위해 사용됩니다.

예를 들어 다음과 같습니다.http://site.tld/blog/novinka.

그것은 의 번역이다."блог, новинка"영어로는 대략"blog", "latest".

아시다시피 러시아어로 '블로그'는 '블로그'로 번역됩니다.그 말은 즉, 첫 번째 부분은[:query](최적의 시나리오에서는) 최종적으로는,['en', 'ru']사용 가능한 언어 목록그리고 다음 코너 '노빈카'를 들어주세요.이 언어에는 다음 중 하나의 언어만 포함될 수 있습니다.['ru'].

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

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

그 이외의 모든 것이 실패했을 경우는, 사이트의 디폴트 언어를 선택합니다.

매개 변수로서의 언어

대체 방법은 URL을 사용하는 것입니다.이 URL은 다음과 같이 정의할 수 있습니다.http://site.tld/[:language]/[:query]이 경우 쿼리를 번역할 때 언어를 추측할 필요가 없습니다.왜냐하면 그 시점에서 어떤 언어를 사용해야 하는지 이미 알고 있기 때문입니다.

두 번째 언어 소스인 cookie 값도 있습니다.그러나 여기서 Accept-Language 헤더를 조작하는 것은 의미가 없습니다.콜드 스타트(사용자가 커스텀 쿼리로 사이트를 처음 여는 경우)의 경우 알 수 없는 수의 언어를 다루고 있지 않기 때문입니다.

대신, 3가지 단순하고 우선순위가 높은 옵션이 있습니다.

  1. 한다면[:language]세그먼트(segment)가 설정되어 있습니다.사용합니다.
  2. 한다면$_COOKIE['lang']설정되었으므로 사용
  3. 기본 언어를 사용하다

언어를 사용할 경우 쿼리를 변환하려고만 하면 됩니다.변환이 실패했을 경우 라우팅 결과에 따라 특정 세그먼트에 대해 "기본값"을 사용합니다.

세 번째 방법은 없나요?

네, 기술적으로 두 가지 접근 방식을 결합할 수 있지만, 이 경우 프로세스가 복잡해지고 웹 사이트의 URL을 수동으로 변경하려는 사용자만 수용할 수 있습니다.http://site.tld/en/news로.http://site.tld/de/news뉴스 페이지가 독일어로 바뀔 것으로 예상합니다.

그러나 이 경우에도 쿠키 값(이전 언어 선택에 대한 정보를 포함)을 사용하여 마법과 희망을 덜 가지고 구현할 수 있습니다.

어떤 접근방식을 사용할까요?

이미 짐작하셨겠지만 제가 추천하는 건http://site.tld/[:language]/[:query]더 합리적인 선택지로서요.

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

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

이 경우'/news/article/121415'쿼리일 것입니다.'EU-as-global-reserve-currency'제목입니다.순수하게 SEO를 위한 것입니다.

라라벨에서 할 수 있나요?

그런 셈이죠, 하지만 디폴트로는 아니에요.

잘 모르지만, 제가 본 바로는, Larabel은 단순한 패턴 기반의 라우팅 메커니즘을 사용하고 있습니다.다국어 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을 사용하여 최소한의 퍼포먼스로 대량의 트래픽 부하를 처리하는 방법에 영감을 주었습니다.ues.

PHP에서 알 수 있듯이 모든 번역 작업에 대해 함수를 호출하는 대신, 우리는 플레이스 홀더로 기본 파일을 정의하고 그 파일을 캐시하기 위해 프리 프로세서를 사용합니다(파일 수정 시간을 저장하여 항상 최신 콘텐츠를 제공합니다).

변환 태그

Thomas는 다음과 같이 사용합니다.{tr}그리고.{/tr}tags를 사용하여 변환의 시작점과 종료점을 정의합니다.저희가 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 파일을 작성하기 위해 이와 같은 Just-in-Time '컴파일러'(그러나 실제로는 프리프로세서)이렇게 하면 파일 내의 모든 문자열에 대해 변환 함수를 호출하는 대신 기본적으로 변환된 파일을 캐시할 수 있습니다.

// 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;
}

주의: regex가 동작하는 것을 검증하지 않았습니다.또, 당사의 서버로부터 카피한 것도 아닙니다만, 조작의 구조를 알 수 있습니다.

호출 방법

다시 말씀드리지만, 이 예는 제가 아니라 토마스 블리의 것입니다.

// 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언어를 덮어쓰는 매개 변수이지만 언어별 하위 도메인 또는 언어별 페이지 수는 권장하지 않습니다. 그러면 어떤 페이지가 인기 있는지 알아보기 어려워지고 인바운드 링크의 값도 줄어들기 때문입니다.

왜 이 방법을 사용하는가?

이 전처리 방법이 마음에 드는 이유는 다음 3가지입니다.

  1. 거의 변경되지 않는 콘텐츠에 대해 많은 기능을 호출하지 않음으로써 큰 퍼포먼스가 향상됩니다(이 시스템에서는 10만 명의 프랑스어 방문자가 번역 교체를 한 번만 실행할 수 있습니다).
  2. 단순한 플랫 파일을 사용하고 순수 PHP 솔루션이기 때문에 데이터베이스에 부하가 걸리지 않습니다.
  3. 번역 내에서 PHP 식을 사용할 수 있습니다.

번역된 데이터베이스 콘텐츠 가져오기

데이터베이스에 콘텐츠 컬럼을 추가할 뿐입니다.language다음으로 액세스 방식을 사용합니다.LANG(ZF1을 사용한) SQL 콜은 다음과 같습니다.

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

이 기사에는 복합 프라이머리 키가 있습니다.id그리고.languageso 기사54는 모든 언어로 존재할 수 있습니다.우리들의LANG디폴트로 하다en_US를 지정합니다.

URL 슬래그 변환

여기에서는 두 가지를 조합합니다.하나는 부트스트랩에 있는 기능으로$_GET언어 파라미터로 cookie 변수를 덮어씁니다.또 하나의 파라미터는 여러 slug를 받아들이는 라우팅입니다.다음으로 라우팅에서 다음과 같은 작업을 수행할 수 있습니다.

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

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

몇 가지 기타 옵션에 관한 주의사항

PHP 기반 On-The-Fly 번역

사전 처리된 번역보다 더 나은 점은 없습니다.

프런트 엔드 기반 번역

저는 오랫동안 이런 것들이 흥미롭다고 생각해왔지만, 몇 가지 주의사항이 있습니다.예를 들어, 번역할 예정인 웹사이트의 전체 구문 목록을 사용자에게 제공해야 합니다.사이트에 숨김 또는 접근을 허용하지 않은 영역이 있는 경우 문제가 발생할 수 있습니다.

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

데이터베이스 기반 변환

PHP의 데이터베이스 연결 속도는 그다지 중요하지 않으며, 이는 모든 구에서 함수를 호출하여 번역해야 하는 이미 높은 오버헤드를 더합니다.이 접근방식에서는 퍼포먼스와 scalability에 관한 문제가 매우 큰 것 같습니다.

휠을 발명하지 말고 gettext와 ISO languages 약자 목록을 사용하는 것이 좋습니다.일반적인 CMS 또는 프레임워크에서 i18n/l10n이 어떻게 구현되었는지 보셨습니까?

gettext를 사용하면 많은 케이스가 이미 여러 형식의 숫자처럼 구현되어 있는 강력한 도구를 얻을 수 있습니다.영어에는 단수와 복수 두 가지 옵션만 있습니다.하지만 러시아어에는 예를 들어 3가지 형태가 있는데 영어만큼 간단하지 않다.

또한 많은 번역자가 이미 gettext를 사용한 경험이 있습니다.

케이크 보기PHP 또는 Drupal. 둘 다 다국어를 사용할 수 있습니다.인터페이스 현지화의 예로서 CakePHP를, 컨텐츠 번역의 예로서 Drupal을 참조해 주세요.

l10n의 경우 데이터베이스를 사용하는 것은 전혀 해당되지 않습니다.문의가 쇄도할 것이다.표준적인 접근법은 메모리 내의 모든 l10n 데이터를 초기 단계(또는 layy loading을 원하는 경우 i10n 함수에 대한 첫 번째 호출 시)에 가져오는 것입니다.한 번에 .po 파일 또는 DB에서 모든 데이터를 읽을 수 있습니다.어레이에서 요청된 문자열만 읽는 것이 아닙니다.

인터페이스를 변환하기 위해 온라인 도구를 구현해야 하는 경우, DB에 모든 데이터를 저장할 수 있지만 모든 데이터를 파일에 저장할 수는 없습니다.메모리의 데이터량을 줄이려면 변환된 모든 메시지/스트링을 그룹으로 분할하고 가능하면 필요한 그룹만 로드합니다.

그럼 3번으로 딱 맞네요단, 일반적으로 컨트롤러별 파일 등이 아닌 하나의 큰 파일입니다.파일을 1개 여는 것이 퍼포먼스에 최적이기 때문입니다.일부 로드된 웹 앱은 포함/요구 호출 시 파일 작업을 피하기 위해 모든 PHP 코드를 하나의 파일에 컴파일합니다.

URL에 대해서Google은 간접적으로 번역을 사용할 것을 제안합니다.

http://example.ca/fr/v elo-de-montagne.http://http://example.ca/fr/v 를 참조해 주세요.

또, 사용자를 디폴트 언어 프리픽스로 리다이렉트 할 필요가 있다고 생각합니다.예를 들어, http://examlpe.com/about-us 는 http://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 스키마와 번역에 적합한 인터페이스를 갖추고 있습니다.마치 당신이 기사를 만들고 그것에 대한 언어를 선택하는 것처럼.그리고 나중에 다른 언어로 번역할 수 있습니다.

Drupal translation interface

URL slug에는 문제가 없다고 생각합니다.민달팽이들을 위한 테이블을 따로 만들면 그것이 올바른 결정일 것이다.또한 적절한 인덱스를 사용하여 대량의 데이터라도 테이블을 조회하는 데 문제가 없습니다.또한 전체 텍스트 검색이 아니라 문자열이 일치하여 slug에 varchar 데이터 유형을 사용할 수 있으며 해당 필드에도 인덱스를 사용할 수 있습니다.

추신, 미안해요, 하지만 제 영어는 완벽하지 않아요.

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

예를 들어 다음과 같은 텍스트가 있다고 가정합니다.

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_namegeoip이 설치되어 있지 않은 경우 로 폴백합니다.HTTP_ACCEPT_LANGUAGE머리글자

서브답변입니다.번역된 URL은 반드시 앞에 언어 식별자를 붙여 사용합니다.http://www.domain.com/nl/over-ons
하이브리드 해법은 복잡해지기 쉬우니까 그냥 계속 할 거예요. 왜요?SEO에서는 URL이 필수적이기 때문입니다.

db 변환에 대해서:언어의 수는 어느 정도 정해져 있습니까?아니면 다소 예측 불가능하고 역동적인가?고정되면 새 열만 추가하거나, 그렇지 않으면 여러 테이블을 사용합니다.

하지만 일반적으로, 왜 Drupal을 사용하지 않는가?누구나 CMS를 구축하고 싶어한다는 것은 알고 있습니다.CMS의 고속화, 슬림화 등입니다.하지만 그건 정말 나쁜 생각이야!

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

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

일반적인 어플리케이션에서 번역할 필요가 있는 콘텐츠의 종류를 이해하는 것은 도움이 됩니다.

1) 클래스(또는 프로시저 코드)로부터의 오류 메시지 2) 클래스(또는 프로시저 코드) 3) 페이지 콘텐츠(통상 데이터베이스에 저장) 4) 사이트 전체 문자열(웹 사이트 이름 등) 5) 스크립트 고유의 문자열

첫 번째 유형은 이해하기 쉽습니다.기본적으로 "데이터베이스에 접속할 수 없습니다..."와 같은 메시지에 대해 설명합니다.이러한 메시지는 오류가 발생한 경우에만 로드해야 합니다.매니저 클래스는 다른 클래스로부터 콜을 수신하고 파라미터로 전달된 정보를 사용하여 관련 클래스 폴더로 이동하여 오류 파일을 가져옵니다.

두 번째 유형의 오류 메시지는 폼 검증이 잘못되었을 때 표시되는 메시지("공백으로 둘 수 없습니다" 또는 "5자 이상의 암호를 선택하십시오")와 비슷합니다.클래스를 실행하기 전에 문자열을 로드해야 합니다.뭔지 알아

실제 페이지 내용은 언어별로 1개의 테이블을 사용하며, 각 테이블에는 언어의 코드가 앞에 붙습니다.en_content는 영어 콘텐츠를 포함한 테이블, es_content는 스페인, cn_content는 중국, fr_content는 프랑스어입니다.

네 번째 문자열은 웹사이트 전체에 적용됩니다.이는 언어 코드(en_lang.php, es_lang.php 등)를 사용하여 명명된 Configuration파일을 통해 로드됩니다.글로벌 언어 파일의 영어 글로벌 파일에는 array('영어', '중국어', '스페인어', '프랑스어' 등 번역된 언어를, 프랑스어 파일에는 array('Anglais', 'Chino', 'Espagnol', 'Francais' 등)를 로드해야 합니다.따라서 언어 선택 드롭다운을 입력하면 올바른 언어로 표시됩니다.

마지막으로 스크립트 고유의 문자열이 표시됩니다.그래서 요리 어플을 쓰면 '당신의 오븐은 충분히 뜨겁지 않았다' 이런 식으로 나올 수도 있어요.

내 어플리케이션 사이클에서는 글로벌 언어 파일이 먼저 로드됩니다.여기에는 글로벌 문자열('Jack's Web 사이트' 등)뿐만 아니라 일부 클래스의 설정도 있습니다.기본적으로 언어나 문화에 의존하는 모든 것.이 문자열에는 날짜 마스크(MMDDYYY 또는 DDMMYYY) 또는 ISO 언어 코드가 포함되어 있습니다.주요 언어 파일에 개별 수업의 문자열이 포함되어 있습니다. 왜냐하면 너무 적기 때문입니다.

디스크에서 읽는 두 번째 및 마지막 언어 파일은 스크립트 언어 파일입니다.lang_en_home_어서오세요.php는 홈/웰컴 스크립트의 언어 파일입니다.스크립트는 모드(홈)와 액션(환영)으로 정의됩니다.각 스크립트에는 설정 파일과 언어 파일이 있는 자체 폴더가 있습니다.

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

문제가 발생했을 경우 관리자는 언어 의존 오류 파일을 어디서 얻을 수 있는지 알고 있습니다.그 파일은 에러가 발생했을 경우에만 로드됩니다.

그래서 결론은 명백하다.응용 프로그램 또는 프레임워크 개발을 시작하기 전에 번역 문제에 대해 생각해 보십시오.번역이 포함된 개발 워크플로우도 필요합니다.저는 제 프레임워크로 사이트 전체를 영어로 작성한 후 모든 관련 파일을 번역합니다.

번역 문자열 구현 방법에 대한 간단한 마지막 한마디입니다.제 프레임워크에는 $manager라는 단일 글로벌 매니저가 있으며, 이 매니저는 다른 모든 서비스에서 사용할 수 있는 서비스를 실행합니다.예를 들어 폼서비스는 html 서비스를 취득하여 이를 사용하여 html을 작성합니다.제 시스템의 서비스 중 하나는 번역 서비스입니다.$codeator-> set service, $code, $string)은 현재 언어의 문자열을 설정합니다.언어 파일은 이러한 문장의 목록입니다.$translationator-> get service, $code)는 변환 문자열을 가져옵니다.$code는 1과 같은 숫자이거나 'no_connection'과 같은 문자열일 수 있습니다.번역자의 데이터 영역에는 각각 독자적인 네임스페이스가 있기 때문에 서비스 간에 충돌이 발생할 수 없습니다.

몇 년 전처럼 바퀴를 재발명하는 일을 누군가를 구할 수 있기를 바라며 이 글을 올렸습니다.

Symfony Framework를 사용하기 전에 같은 Probem을 가지고 있었습니다.

  1. arameters pageId(또는 #2에 기재된 objectId, objectTable), 타깃 언어 및 폴백(디폴트) 언어의 옵션 파라미터를 가진 함수 __()를 사용합니다.디폴트 언어는 나중에 쉽게 변경할 수 있도록 일부 글로벌Configuration에서 설정할 수 있습니다.

  2. 데이터베이스 내 콘텐츠 저장에는 (pageId, 언어, 콘텐츠, 변수)라는 구조를 사용했습니다.

    • pageId는 번역하고 싶은 페이지의 FK가 됩니다.뉴스, 갤러리 등 다른 오브젝트가 있는 경우 objectId, objectTable의 2개의 필드로 분할합니다.

    • language - ISO 언어 문자열 EN_en, LT_lt, EN_us 등이 저장됩니다.

    • content - 변수 치환을 위해 와일드카드와 함께 번역하는 텍스트.예: "%%name%%님 안녕하세요"고객님의 계좌 잔액은 %%%%입니다.

    • variables - json 부호화 변수.PHP는 이러한 내용을 빠르게 해석할 수 있는 기능을 제공합니다.예: "이름: Laurynas, 잔액: 15.23"

    • 슬러그 필드도 언급하셨잖아요이 테이블에 자유롭게 추가하여 빠르게 검색할 수 있습니다.

  3. 변환 캐싱을 통해 데이터베이스 콜을 최소로 줄여야 합니다.PHP 언어에서 가장 빠른 구조이기 때문에 PHP 배열에 저장해야 합니다.이 캐시를 만드는 방법은 사용자에게 달려 있습니다.제 경험상 지원되는 언어별로 폴더와 페이지 ID별로 배열이 있어야 합니다.캐시는 변환을 갱신한 후에 재구축해야 합니다.변경된 어레이만 재생성해야 합니다.

  4. 2번에서 대답한 것 같아

  5. 당신의 생각은 완벽하게 논리적이다.이건 꽤 간단해서 문제될 건 없을 것 같아.

URL은 변환테이블에 저장되어 있는 slug를 사용하여 번역해야 합니다.

마지막 말

베스트 프랙티스를 조사하는 것은 항상 좋지만, 수레바퀴를 재창조하는 것은 아닙니다.잘 알려진 프레임워크에서 컴포넌트를 가져와 사용하면 됩니다.

Symfony 번역 컴포넌트를 살펴보십시오.당신에게 좋은 암호 기반이 될 수 있습니다.

몇 번이고 제 자신에게 관련된 질문을 던지고는 격식을 차린 언어로 길을 잃었어요.조금이나마 도움이 되기 위해 몇 가지 조사 결과를 공유하고 싶습니다.

고급 CMS를 볼 것을 권장합니다.

Typo3위해서PHP(여러 가지가 있지만 그게 가장 성숙하다고 생각되는 것 같아)

PlonePython

2013년에는 웹이 다르게 작동해야 한다는 것을 알게 되면 처음부터 다시 시작하세요.즉, 고도의 기술/경험자들로 구성된 팀을 구성하여 새로운 CMS를 구축해야 합니다.그 목적을 위해 폴리머를 검토하는 것이 좋을지도 모릅니다.

코딩 및 다국어 웹사이트/원어 지원에 관해서는 모든 프로그래머가 유니코드에 대한 단서를 가지고 있어야 한다고 생각합니다.Unicode를 모르면 데이터가 엉망이 됩니다.수천 개의 ISO 코드를 사용하지 마십시오.기억만 좀 남길 뿐이죠그러나 UTF-8로 문자 그대로 모든 것을 할 수 있으며 중국어 문자도 저장할 수 있습니다.단, 이를 위해서는 기본적으로 utf-16 또는 utf-32를 만드는 2바이트 또는 4바이트 문자를 저장해야 합니다.

URL 인코딩에 관한 것이라면 인코딩을 혼재시키지 말고 적어도 도메인 이름에는 브라우저와 같은 애플리케이션을 제공하는 다양한 로비에 의해 정의된 규칙이 있습니다.예를 들어 도메인은 다음과 같이 매우 유사할 수 있습니다.

ankofamerica.com 또는 bankofamerica.com samesbutdifferent;)

물론 모든 인코딩을 사용하려면 파일 시스템이 필요합니다.utf-8 파일 시스템을 사용한 Unicode의 또 다른 플러스.

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

내가 아는 가장 성숙한 프레임워크라면 MVC 같은 일반적인 일을 하는 것은 정말 싫다!"퍼포먼스"와 같이 무언가를 팔고 싶다면 "퍼포먼스"와 "기능 풍부"이라는 단어를 사용하여 판매하십시오.(도대체)란Zendphp 카오스 코더에 표준을 가져오는 것은 좋은 일이라는 것이 증명되었습니다.그러나 typeo3는 CMS 외에 프레임워크도 가지고 있습니다.최근에 재개발되어 flow3라고 불리고 있습니다.물론 프레임워크는 데이터베이스 추상화, 템플릿 및 캐싱 개념을 포함하지만 개별적인 강점이 있습니다.

캐시에 관한 것이라면...놀랍도록 복잡하거나 다층적일 수 있습니다.PHP에서는 accellerator, opcode뿐만 아니라 html, httpd, mysql, xml, css, js도 생각할 수 있습니다.모든 종류의 캐시물론 일부 부분은 캐시에 저장해야 하며 블로그 답변과 같은 동적 부분은 캐시에 저장해서는 안 됩니다.일부는 생성된 URL을 사용하여 AJAX를 통해 요청해야 합니다.JSON, 해시방

그런 다음 웹 사이트의 작은 컴포넌트를 특정 사용자만 액세스 또는 관리하도록 하고 싶기 때문에 개념적으로 큰 역할을 합니다.

또, 통계 작성, 분산 시스템/페이스북등의 소프트웨어를 사용하고 싶은 경우도 있습니다.상위 cms를 통해 구축되는 소프트웨어는 모두...메모리, 빅데이터, xml 등 다양한 유형의 데이터베이스가 필요합니다.

글쎄, 지금은 그걸로 충분하다고 생각해.만약 여러분이 Type3/plone이나 언급된 프레임워크에 대해 들어본 적이 없다면, 여러분은 충분히 공부할 수 있습니다.그 과정에서 당신은 아직 질문하지 않은 질문들에 대한 많은 해결책을 찾을 수 있을 것이다.

2013년도와 php가 곧 죽을 것이기 때문에 새로운 CMS를 만들자고 생각한다면 다른 개발자 그룹에 가입하는 것을 환영한다.

행운을 빕니다.

그리고 또.미래에 사람들이 웹사이트를 더 이상 갖지 않는 것은 어떨까?모두 구글 플러스에 접속할 수 있을까요?개발자들이 좀 더 창의적으로 (보글에 동화되지 않기 위해) 쓸모 있는 일을 했으면 좋겠다.

//// 편집 // 기존 응용 프로그램에 대해 조금만 생각해 보십시오.

php mysql CMS가 있고 멀티랭 지원을 포함시키고 싶은 경우.임의의 언어에 대한 추가 컬럼과 테이블을 함께 사용하거나 오브젝트 ID와 언어 ID를 가진 변환을 같은 테이블에 삽입하거나 모든 언어에 대해 동일한 테이블을 만들고 오브젝트를 삽입한 후 모두 표시할 경우 선택 결합을 만들 수 있습니다.데이터베이스에는 utf8 general ci를 사용하고 전면/백엔드에는 utf8 text/encoding을 사용합니다.저는 당신이 이미 설명한 방법으로 URL의 URL 경로 세그먼트를 사용했습니다.

domain.org/en/about 콘텐츠 테이블에 언어 ID를 매핑할 수 있습니다.URL의 파라미터 맵이 필요하기 때문에 URL의 패스 세그먼트에서 매핑할 파라미터를 정의합니다.

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

룩업 컨피규레이션

pageid |url

1 | / about / displays / .. / .

1 | / .. / about / .... 、 / ../

url path segment "에 파라미터를 매핑합니다.

$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을 "재작성"해야 합니다.

다국어 웹사이트를 만들 때 가장 어려운 점은 콘텐츠입니다.같은 기사의 다른 버전을 어떻게 저장합니까?관계형 데이터베이스를 사용하고 있습니까, 아니면 비관계형 데이터베이스를 사용하고 있습니까?

MySQL과 같은 관계형 DB를 사용하면JSON동일한 필드의 모든 다른 버전을 모두 저장합니다.

비관계형 DB를 사용하는 경우 키로 식별할 수 있는 동일한 개체에 다른 버전을 저장할 수 있습니다.

Laravel을 사용하는 경우 기존 관계형 데이터베이스를 사용할 때 Laravel Translatable 패키지가 유용할 수 있습니다.

정적 콘텐츠를 호스팅하는 경우 Google의 Firebase Hosting은 다음과 같은 국가, 언어 또는 언어 + 국가별 콘텐츠를 반환하는 i18n 호스팅 규칙을 지원합니다.index.html,404.html또는manifest.json파일(프로그레시브 웹 앱용)을 참조하십시오.

호스트에서는 사용자의 IP 주소에 따라 국가를 선택하고 브라우저의 IP 주소에 따라 언어를 선택합니다.Accept-Language다음으로 우선 순위 규칙을 적용하여 요청된 각 파일 내용을 반환합니다.

  1. 언어 코드 + 국가 코드(예를 들어 fr_ca/의 콘텐츠)

  2. 국가 코드만(예를 들어 ALL_ca/의 콘텐츠)

  3. 언어 코드만(예를 들어 fr/ 또는 es_ALL/의 콘텐츠)

  4. 공용 디렉토리의 루트와 같이 "i18n 내용" 디렉토리 외부에 있는 "기본" 내용입니다.

규칙 1과 규칙 3은 요청 언어별 품질 값 순서로 적용됩니다.Accept-Languageheader를 클릭합니다.

public/
    index.html  // Default homepage
    manifest.json  // Default manifest.json
    404.html  // Default custom 404 page

    localized-files/
        ALL_ca/
            index.html
        es_ALL/
            index.html
            404.html
            manifest.json   << Spanish
        fr/
            index.html
            404.html
            manifest.json   << French
        fr_ca/
            index.html
            manifest.json
// firebase.json

"hosting": {

  "public": "public",

  "ignore": [
    "firebase.json",
    "**/.*",
    "**/node_modules/**"
  ],

  "i18n": {
    "root": "/localized-files"  // <<< "i18n content" folder
  }

  ...
}

전체 세부 정보...

데이터베이스 작업:

언어 테이블 '언어' 만들기:

필드:

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를 기반으로 데이터베이스 테이블 '콘텐츠'에서 데이터를 가져옵니다.

상세한 것에 대하여는, http://skillrow.com/multilingual-website-in-php-2/ 를 참조해 주세요.

퀘벡에 사는 사람으로서 거의 모든 사이트가 프랑스어와 영어입니다.WP를 위한 다국어 플러그인을 많이 시도하고 있습니다.내 모든 사이트에서 사용할 수 있는 유일한 유용한 솔루션은 mQtranslate입니다.나는 그것과 함께 살고 죽는다!

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

WORDPRESS + (플러그인)는 어떻습니까?사이트의 구조는 다음과 같습니다.

  • 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

p.s. 단, 플러그인이 아직 활성화 되어 있는지 확인합니다.

Javascript를 업로드할 수 있는 웹 사이트에서 사용할 수 있는 매우 간단한 옵션은 www.multilingualizer.com입니다.

모든 언어의 모든 텍스트를 한 페이지에 넣은 후 사용자가 볼 필요가 없는 언어를 숨길 수 있습니다.효과가 좋다.

언급URL : https://stackoverflow.com/questions/19249159/best-practice-multi-language-website

반응형