모범 사례 다국어 웹 사이트


179

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

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

꽤 오랫동안 사용해온 콘텐츠 관리 시스템을 업그레이드 / 재개발하려고합니다. 그러나, 나는 다국어가이 시스템에서 크게 개선되었다고 생각합니다. 프레임 워크를 사용하지 않고 다음 프로젝트에 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:

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


답변:


115

주제의 전제

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

  • 인터페이스 번역
  • 함유량
  • 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 차 언어 소스도 있습니다. 그러나 "cold start"(사용자가 처음으로 사이트를 사용자 정의 쿼리로 열 때)의 경우 알 수없는 언어를 처리하지 않기 때문에 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 );

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


1
또 다른 통찰력에 감사드립니다! 매우 사려 깊은! URL에 언어 매개 변수가 있다고 생각했습니다. 이것은 단순히 사용자뿐만 아니라 SEO 목적으로도 특정 언어를 식별하는 가장 좋은 방법입니다. 사용자가 / en / news를 / de / news로 변경하는 경우 내 아이디어는 예를 들어 / de / nachrichten으로 301 (영구적) 리디렉션을 수행하는 것입니다. 각 언어가 페이지 당 하나의 고유 한 URL을
갖도록

최상의 답변을 선택하기가 점점 더 어려워지고 있습니다. 현재 현상금의 적어도 일부를받을 가치가있는 약 3/4 답변이 있습니다. 그들이 :) 내가 함께 정리하고 싶었 모든 것에 대한 확고한 답이 될 결합
Pendo - 여호수아

URL 번역에 대해 제공 한 세부 답변에 대해 최소한 추가 답변을 드리기 위해 귀하의 답변을 수락했습니다. 대단히 감사합니다! 그러나 현상금은 플랫폼 독립적 인 방식으로 내 질문의 모든 측면에 답변했기 때문에 아래 사람에게 수여됩니다.
Joshua-Pendo

52

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

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

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

번역 태그

Thomas는 {tr}{/tr}태그를 사용하여 번역이 시작되고 끝나는 위치를 정의합니다. TWIG를 사용 {하고 있기 때문에 혼동을 피하기 위해 사용하고 싶지 않으므로[%tr%] 하고 [%/tr%]대신. 기본적으로 다음과 같습니다.

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

Thomas는 파일에서 기본 영어를 사용하도록 제안합니다. 영어로 값을 변경하면 모든 번역 파일을 수정하지 않아도되므로이 작업을 수행하지 않습니다.

INI 파일

그런 다음 각 언어에 대한 INI 파일을 다음 형식으로 만듭니다 placeholder = translated.

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

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

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

사용자가 CMS 내에서이를 수정할 수 있도록하는 것은 쉽지 않습니다 preg_split.\n=와 INI 파일에 쓸 수 CMS가 수 만들기.

전 처리기 구성 요소

기본적으로 Thomas는 변환 파일을 가져 와서 디스크에 정적 PHP 파일을 작성하기 위해 적시에 '컴파일러'(실제로는 전 처리기 임) 기능을 사용할 것을 제안합니다. 이런 식으로 파일의 모든 문자열에 대해 번역 함수를 호출하는 대신 번역 된 파일을 기본적으로 캐시합니다.

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

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

참고 : 정규식이 작동하는지 확인하지 않았으며 회사 서버에서 복사하지 않았지만 작업 작동 방식을 볼 수 있습니다.

그것을 부르는 방법

다시 한 번,이 예는 나로부터가 아니라 Thomas Bley의 것입니다.

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

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

쿠키 (또는 쿠키를 얻을 수없는 경우 세션 변수)에 언어를 저장 한 다음 모든 요청에서 언어를 검색합니다. 이것을 옵션과 결합 할 수 있습니다$_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의 데이터베이스 연결 속도는 집에 쓸만한 것이 아니며, 이는 번역 할 모든 구절에서 함수를 호출하는 이미 높은 오버 헤드를 추가합니다. 이 접근 방식에서는 성능 및 확장 성 문제가 압도적입니다.


화면에서 번역 된 문자열을 구문 분석하는 방법 인 "프론트-엔드 번역"과 혼동 한 것을 알 수 있습니다. 클라이언트 측에서 번역 할 방법을 찾지 못했습니다! 내가 의미하는 것은 프론트 엔드에서 언어를 전환하는 가장 쉬운 방법이지만 쿠키 나 사용자 설정을 사용하는 것이 분명합니다 :)
Joshua-Pendo

오, 그리고 Database-Driven은 모든 번역을 관리하는 방법을 목표로하고 있었으므로 이상적인 해결책은 데이터베이스에 번역을 쓰는 백엔드이며 PHP를 생성하는 전처리 구성 요소를 생성하는 함수입니다 파일. Why?: simple .. 텍스트를 조금만 변경해도 귀찮게하고 싶지 않습니다. 사용자는 코드 편집기 나 ftp 프로그램을 사용하지 않고도 스스로 할 수 있어야합니다. :)
Joshua-Pendo

@PENDO 저는 당신이 프론트 엔드 번역을 의미하지 않았다는 것을 알고 있습니다. 이것은 JS를 사용하여 프론트 엔드 번역 프레임 워크를 제안한 사용자에 대한 얇은 설명입니다. ;)
글리치 욕망

@ PENDO 동의합니다. 나는 당신이 제안한대로 백엔드를 사용하지만 데이터베이스 대신 성능상의 이유로 플랫 파일을 사용한다는 것에 동의합니다. 물론 여기서 핵심 제안 사항은 변경시 템플릿을 미리 렌더링하여 .INI파일을 3 열 데이터베이스 테이블 ( placeholder,, replacement)로 바꿀 수 있다는 것 language입니다. placeholder및의 복합 키 language. 그런 다음 tempfile(경로에 대한 경로) 및 modified(날짜 시간) 이있는 다른 2 열이 있습니다.
글리치 욕망

1
@PENDO 감사합니다. 250 개를 백업했으며 24 시간 내에 사이트에서 허용하는 두 가지 답변을 모두 선택했을 때 teresko에게이를 수여 할 계획이며 분할이 귀하의 의도를 가장 잘 반영한다고 생각합니다.
Glitch Desire

15

바퀴를 발명하지 말고 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 죄송합니다. 제 영어는 완벽하지 않습니다.


내 질문에 대답하는 데 시간을 내 주셔서 감사합니다. 당신의 영어는 이해하기에 충분합니다! 나는 당신의 노력으로 이미 당신을 +1 할 것입니다!
Joshua-Pendo

Yaroslav, 다시 한 번 답변 해 주셔서 감사합니다. 그러나 나는 2 개의 다른 답변을 가지고 조금 더 완전하고 코드 뒤에 사용 된 메소드를 설명하는 대신 이미 설명했다.
Joshua-Pendo

2
문제 없어요. 실제로 그 대답은 나에게도 더 완전하고 흥미로운 답변입니다. 그러나 나는 당신이 내 대답에서 유용한 것을 얻었기를 바랍니다.
Yaroslav

12

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

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

Welcome!

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

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

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

$_COOKIE['language'];

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

$language=$_COOKIE['language'];

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

echo $welcome[$language];

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

Croeso!

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


1
이것은 내가 요청한 답변 근처에 없습니다. 또한 각 페이지에서 모든 언어를 사용할 수있는 것보다 lang.en.php포함 된 파일을 만들고 $lang['welcome']각 파일에 선언 된 파일을 사용 하는 것이 좋습니다.
여호수아-Pendo

7

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

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

개체 : 로캘 \ 로캘

<?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헤더

어떤 방법으로 데이터베이스가 지저분합니까? 다른 언어로 가능한 문자 때문에? 지금까지 나는 주로 영어, 프랑스어, 네덜란드어, 독일어 웹 사이트를 가지고 있으므로 지금은 문제가되지 않습니다. 답변 주셔서 감사합니다, 그러나 그것은 답변의 일부이기 때문에 현상금을 이길 수 없습니다.
Joshua-Pendo

글쎄, 나는 당신의 질문이 당신에게만 도움이 될 것이라고 생각합니다 힌디어, 태국어, 중국어 및 아랍어와 같은 언어 (이 언어는 문자를 나타내는 데 1 바이트가 더 걸릴 것입니다)는 필요한 언어에 대해서만 고려할 것입니다. db를 사용하는 경우 utf8_general_ci데이터 정렬이 적절한 방법입니다.
Shushant

동의합니다. 저 자신도 약간의 트랙을 가지고 있습니다. 지적 해 주셔서 감사합니다. 또한 멀티 비트 문자도이 질문에 언급 될만큼 중요합니다.)
Joshua-Pendo

5

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

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

그러나 일반적으로 Drupal을 사용하지 않는 이유는 무엇입니까? 나는 모두가 자신의 CMS를 만들고 싶어한다는 것을 알고 있습니다. 더 빠르고, 더 빠르거나 등등입니다. 그러나 그것은 정말 나쁜 생각입니다!


1
답변 주셔서 감사합니다. Drupal / Joomla를 사용하고 싶지 않은 이유는 간단합니다. 시스템의 모든 기능과 일시 중지 가능한 결함, 코드 작성 방법 (및 중요 : 300 명의 프로그래머가 함께 빌드하지 않음)을 알고 싶습니다. . 오픈 소스를 선택하지 않을 충분한 이유가 있습니다. 그 외에도 회사가 고객에게 중요한 요소가되기를 원합니다. 다른 개발자에게 가서 아무 것도 남기지 않는 것은 나쁜 일입니다.
Joshua-Pendo

7
나는이 모든 이유들이 수많은 기사들에 대해 논쟁을 벌이고 있다고 생각한다. 고객은 다른 사람이 관리 할 수없는 독점 CMS를 가지고 있기 때문에 귀하를 선택하지 않기를 바랍니다. 그러나 어쨌든 그것은 완전히 다른 토론입니다.
Remy

1
나는 당신의 요점을 이해하지만 여전히 모든 인과 아웃을 알고있는 시스템을 선호하며 플러그인을 사용할 때 다른 사람의 작업에 의존하는 것에 대해 아무런 느낌이 없습니다.
Joshua-Pendo

1
게다가, 나는 "한 사람의 군대"이기 때문에 내 일을 충분히 문서화하는 경향이있다. 나를 위해 일하는 사람들은 그 시스템을 알기에 어려움을 겪지 않아야한다.
Joshua-Pendo

나쁜 생각은 Drupal을 선택하는 것이며 Google조차도 URL이 번역되는지 여부는 신경 쓰지 않는다고 말합니다. 로케일 식별자를 포함해야합니다.
undefinedman

5

나는 이미 주어진 답을 다듬 으려고하지 않을 것이다. 대신 내 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'과 같은 문자열 일 수 있습니다. 서비스마다 번역기의 데이터 영역에 고유 한 네임 스페이스가 있으므로 서비스간에 충돌이 없습니다.

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


4

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

  1. arameters pageId (또는 # 2에 설명 된 objectId, 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를 살펴보십시오 . 좋은 코드 기반이 될 수 있습니다.


의견을 보내 주셔서 감사합니다. 내가 실수하지 않은 경우 라 라벨 (내 경우)은 Symfony 부품을 사용하므로 바퀴를 재발 명하지 않는 것이 절대적입니다. 내가, 내가 :-) 거기 모범 사례가 많이있다 믿기 시작 해요이 질문에 (그리고 현상금) 다른 사람이 번역을 할 방법에 약간의 통찰력을 얻기 위해 시작
여호수아 - Pendo

1

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

고급 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 (일반적으로 미워하는 단어! "퍼포먼스"와 같은)를하기 위해 퍼포먼스와 피처 리치라는 단어를 사용하고 ... 지옥)입니다 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을 "다시 작성"해야합니다.


의견에 감사드립니다. 내가 생각해야 할 것이 가장 확실합니다. 나는 이미 2 년 동안 utf8 인코딩을 사용해 왔으며 문자로 한 번 어려움을 겪었습니다. 우리가 처음부터 코딩하는 것처럼 플랫폼 독립적 인 방법.
Joshua-Pendo

처음부터 코드를 작성하고 싶다면 Dartlang과 폴리머를 살펴 보는 것이 좋습니다. dartlang은 브라우저에서 작동하며 32 비트 및 64 비트를 지원하며 서버 측에서 대부분의 용도로 사용할 수 있으며 dart2js 컴파일러가 실제로 연구 할 가치가 있습니다. 사람들이 플랫폼 독립성에 대해 이야기하면 자바에 대해 생각합니다 ... 우리는 그 의미를 알고 있습니다. Buildprocess ... 교환을 위해 JSON을 사용한다고 생각합니다. hashbangs 및 serverside를 통해 생성 된 웹 사이트 클라이언트 측. 협업을 보장하려는 모든 작업을 수행하십시오.
Dr. Dama

데이터베이스 레이아웃 및 생성 논리가 주요 작업입니다. 아무도 당신을 위해 여기서 그렇게하지 않을 것입니다 ...하지만 아이디어 자체가 중요합니다. 나는 로비에 신경 쓰지 않고 일을 끝내기 위해 모델을 만들고 물건을 공유 할 수 있기를 바랍니다. 지금 비슷한 작업을하고 있습니다. 하지만 아직 계획 중입니다. Typo3을 백엔드로 고려하고 새로운 클라이언트 구조를 만듭니다. 다국어 패턴은 백엔드에서 해결되며 검색 엔진 / 웹 서비스 전용 정보를 공유합니다. 어쨌든 그것의 모든 맥락과 지속적인 건물 작업
Dr. Dama

-1

데이터베이스 작업 :

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

필드:

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


1
이것은 간단한 언어 통합에 필요한 방법입니다. 전체 게시물을 읽고 답을 보려고 했습니까?
Joshua-Pendo

-2

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

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


1
그래, WP는 그 질문의 어떤 요소도 아니었다. 이 코멘트 aswel 수 있었다
Pendo - 여호수아

-3

무슨 일 워드 프레스 + 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

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


3
스페인어에서 "Holla userio"는 "Hola Usuario"
bheatcoker

1
Lol Holla userio, 재미있었습니다!
spekdrum

내가 스페인어 (그냥 사용 된 예)를 몰랐던 이유 때문에, 서둘러 downvote !! :)
T.Todua 2016 년

-5

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

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


SEO는 매우 나쁠 것입니다! 또한 모든 콘텐츠를로드하는 동안 일부는 필요합니다. 실제로 나쁜 습관입니다.
Hafenkranich

사이트가 영어로만 제공되는 이상한 것들 ... 왜 솔루션을 사용하지 않습니까?
eduardo.lopes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.