Best practice multi language website

Zmagam się z tym pytaniem od kilku miesięcy, ale nie byłem w sytuacji, w której musiałem zbadać wszystkie możliwe opcje wcześniej. W tej chwili czuję, że nadszedł czas, aby poznać możliwości i stworzyć własne preferencje do wykorzystania w nadchodzących projektach.

Pozwól mi najpierw naszkicować sytuację, której szukam

Mam zamiar uaktualnić / przebudować system zarządzania treścią, który używam już od dłuższego czasu. Jednak mam wrażenie, że wiele języków jest wielkim ulepszeniem tego systemu. Wcześniej nie używałem żadnych frameworków, ale zamierzam użyć Laraval4 w nadchodzącym projekcie. Laravel wydaje się najlepszym wyborem czystszego sposobu kodowania PHP. Sidenote: Laraval4 should be no factor in your answer. Szukam ogólnych sposobów tłumaczenia, które są niezależne od platformy / frameworku.

Co powinno być przetłumaczone

Ponieważ system, którego szukam, musi być jak najbardziej przyjazny dla użytkownika, metoda zarządzania tłumaczeniem powinien być w CMS. Nie powinno być potrzeby uruchamiania połączenia FTP, aby modyfikować pliki tłumaczeń lub dowolne szablony HTML/php przetwarzane.

Ponadto Szukam najprostszego sposobu na przetłumaczenie wielu tabel baz danych, być może bez konieczności tworzenia dodatkowych tabel.

Co ja sobie wymyśliłem

Jak już Szukałem, czytając i próbując rzeczy już. Mam kilka opcji. Ale nadal nie czuję się jakbym osiągnąłem najlepszą metodę praktyki dla tego, czego naprawdę Szukam. W tej chwili, to jest to, co wymyśliłem, ale ta metoda ma również skutki uboczne.

  1. PHP Parsed Templates : system szablonów powinien być parsowany przez PHP. W ten sposób mogę wstawić przetłumaczone parametry do HTML bez konieczności otwierania szablonów i modyfikowania ich. Poza tym, PHP parsed templates daje mi możliwość posiadania 1 szablonu dla całej witryny zamiast podfolderu dla każdego języka (który miałem wcześniej). Sposobem osiągnięcia tego celu może być Smarty, TemplatePower, Laravel ' s Blade lub dowolny inny parser szablonów. Jak powiedziałem, powinno to być niezależne od pisemnego rozwiązania.
  2. bazodanowe : być może nie muszę wspominać o tym ponownie. Ale rozwiązanie powinno być oparte na bazie danych. CMS ma być zorientowany obiektowo i MVC, więc musiałbym pomyśleć o logicznej strukturze danych dla łańcuchów. Jak Moje szablony struktura: templates / Controller / View.php być może ta struktura miałaby największy sens: Controller.View.parameter. Tabela bazy danych będzie miała te pola długie z polem value. Wewnątrz szablonów możemy użyć jakiejś metody sortowania jak echo __('Controller.View.welcome', array('name', 'Joshua')), a parametr zawiera Welcome, :name. Stąd wynik jest Welcome, Joshua. Wydaje się to dobrym sposobem, ponieważ parametry takie jak :name są łatwe do zrozumienia przez edytor.
  3. niskie obciążenie bazy danych : oczywiście powyższy system spowodowałby obciążenie załaduj bazę danych, jeśli te ciągi są ładowane w podróży. Dlatego potrzebuję systemu buforowania, który ponownie renderuje pliki językowe, gdy tylko zostaną edytowane/zapisane w środowisku administracyjnym. Ponieważ pliki są generowane, potrzebny jest również dobry układ systemu plików. Chyba możemy wybrać languages/en_EN/Controller/View.php lub .ini, cokolwiek najbardziej Ci odpowiada. Być może .ini jest nawet przetwarzane szybciej w końcu. To powinno zawierać dane w format parameter=value; . Myślę, że jest to najlepszy sposób, aby to zrobić, ponieważ każdy Widok to jest renderowane może zawierać własny plik językowy, jeśli istnieje. Parametry języka powinny być następnie ładowane do określonego widoku, a nie w zasięgu globalnym, aby zapobiec nadpisywaniu się parametrów.
  4. tłumaczenie tabeli bazy danych: w rzeczywistości jest to rzecz, o którą najbardziej się martwię. Szukam sposobu na tworzenie tłumaczeń newsów / stron / itp. jak najszybciej. Posiadanie dwóch tabel dla każdego modułu (na przykład News i News_translations) jest opcją, ale wydaje się, że bardzo praca, aby uzyskać dobry system. Jedną z rzeczy, które wymyśliłem, jest system data versioning, który napisałem: istnieje jedna nazwa tabeli bazy danych Translations, Ta tabela ma unikalną kombinację language, tablename i primarykey. Na przykład: en_pl / News / 1 (odnosi się do angielskiej wersji Wiadomości o ID=1). Ale istnieją 2 ogromne wady tej metody: po pierwsze ta tabela ma tendencję do dość długo z dużą ilością danych w bazie danych, a po drugie byłoby cholernie praca, aby użyć tej konfiguracji do przeszukaj stół. Na przykład szukanie ślimaka SEO przedmiotu byłoby wyszukiwaniem pełnotekstowym, co jest dość głupie. Ale z drugiej strony: jest to szybki sposób na szybkie tworzenie treści, które można przetłumaczyć w każdym stole, ale nie wierzę, że ten pro prześciga przekrętów.]}
  5. Front-end pracy : również front-end wymaga trochę myślenia. Oczywiście będziemy przechowywać dostępne języki w bazie danych i (de)aktywować te, których potrzebujemy. W ten sposób skrypt może wygenerować listę rozwijaną do wyboru język i back-end mogą automatycznie zdecydować, jakie tłumaczenia mogą być wykonane za pomocą CMS. Wybrany język (np. en_pl) będzie następnie używany podczas pobierania pliku językowego dla widoku lub uzyskania odpowiedniego tłumaczenia dla elementu treści na stronie internetowej.
No i są. Moje dotychczasowe pomysły. Nie zawierają nawet opcji lokalizacji dla DAT itp, ale ponieważ mój serwer obsługuje PHP5.3.2+ najlepszą opcją jest użycie rozszerzenia intl, jak wyjaśniono tutaj: http://devzone.zend.com/1500/internationalization-in-php-53 / - ale to byłoby przydatne w każdym późniejszym stadium rozwoju. Na razie głównym problemem jest to, jak najlepiej wykonywać tłumaczenie treści na stronie internetowej.

Poza wszystkim, co tu wyjaśniłem, mam jeszcze jedną rzecz, o której jeszcze nie zdecydowałem, wygląda to na proste pytanie, ale w rzeczywistości przyprawia mnie to o bóle głowy: {]}

Tłumaczenie URL? Powinniśmy to zrobić czy nie? i w czym sposób?

Więc.. jeśli mam ten adres url: http://www.domain.com/about-us i angielski jest moim domyślnym językiem. Czy ten adres URL powinien być przetłumaczony na http://www.domain.com/over-ons, gdy wybieram Holenderski jako mój język? A może powinniśmy pójść prostą drogą i po prostu zmienić zawartość strony widocznej na /about. Ostatnia rzecz nie wydaje się prawidłową opcją, ponieważ generowałaby wiele wersji tego samego adresu URL, Indeksowanie zawartości nie powiedzie się we właściwy sposób.

Inną opcją jest użycie http://www.domain.com/nl/about-us. Generuje to co najmniej unikalny adres URL dla każdej zawartości. Ponadto łatwiej byłoby przejść do innego języka, na przykład http://www.domain.com/en/about-us, A podany adres URL jest łatwiejszy do zrozumienia zarówno dla Google, jak i osób odwiedzających. Korzystając z tej opcji, co robimy z domyślnymi językami? Czy język domyślny powinien usunąć język wybrany domyślnie? Więc przekierowanie http://www.domain.com/en/about-us na http://www.domain.com/about-us ... W moich oczach jest to najlepsze rozwiązanie, ponieważ gdy CMS jest ustawiony tylko dla jednego języka nie ma potrzeby posiadania identyfikacji tego języka w URL.

I trzecia opcja jest kombinacją obu opcji: użycie "language-identification-less" - URL (http://www.domain.com/about-us) dla głównego języka. I użyj URL z tłumaczonym slug SEO dla podanguages: http://www.domain.com/nl/over-ons & http://www.domain.com/de/uber-uns

Mam nadzieję, że moje pytanie sprawi, że wasze głowy pękną, na pewno moje! Pomogło mi to już rozwiązać sprawę jako pytanie. Dał mi możliwość przejrzenia metod, z których korzystałem wcześniej i mam pomysł na mój nadchodzący CMS.

I chciałbym już podziękować za poświęcenie czasu na przeczytanie tego tekstu!

// Edit #1:

Zapomniałem wspomnieć: funkcja _ _ () jest aliasem do tłumaczenia podanego ciągu. W ramach tej metody oczywiście powinna istnieć jakaś metoda awaryjna, w której domyślny tekst jest ładowany, gdy nie ma jeszcze tłumaczeń dostępnych. Jeśli brakuje tłumaczenia, należy go wstawić lub odtworzyć plik tłumaczenia.

Author: Anthony, 2013-10-08

13 answers

Założenie tematu

W wielojęzycznej witrynie istnieją trzy różne aspekty:]}
  • Tłumaczenie interfejsu
  • treść
  • URL routing

Chociaż wszystkie są ze sobą połączone na różne sposoby, z punktu widzenia CMS są zarządzane za pomocą różnych elementów interfejsu użytkownika i przechowywane w różny sposób. Wydaje się, że jesteś pewny swojej realizacji i zrozumienia dwóch pierwszych. Pytanie dotyczyło tego drugiego aspektu- "tłumaczenie URL? Powinien robimy to czy nie? i w jaki sposób?"

Z czego można zrobić URL?

Bardzo ważną rzeczą jest, nie daj się fantazji z IDN . Zamiast Transliteracja (także: transkrypcja i romanizacja). Chociaż na pierwszy rzut oka IDN wydaje się realną opcją dla międzynarodowych adresów URL, w rzeczywistości nie działa tak, jak reklamowano z dwóch powodów: [34]}

  • niektóre przeglądarki zamieniają znaki spoza ASCII, takie jak 'ч' lub 'ž' na '%D1%87' i '%C5%BE'
  • jeśli użytkownik ma niestandardowe motywy, czcionka motywu najprawdopodobniej nie będzie miała symboli dla tych liter]}

Próbowałem podejść do IDN kilka lat temu w projekcie opartym na Yii (okropny framework, IMHO). Napotkałem oba wyżej wymienione problemy przed skrobaniem tego rozwiązania. Podejrzewam też, że to może być wektor ataku.

Dostępne opcje ... tak jak ja je widzę.

Zasadniczo masz dwie opcje, które można streścić jako:

  • http://site.tld/[:query]: gdzie [:query] określa zarówno język, jak i wybór treści

  • http://site.tld/[:language]/[:query]: gdzie [:language] część adresu URL określa wybór języka i [:query] jest używana tylko do identyfikacji treści

Zapytanie to Α i Ω ..

Powiedzmy, że wybierasz http://site.tld/[:query].

W tym przypadku masz jedno główne źródło języka: treść [:query] segmentu; oraz dwa dodatkowe źródła:

  • wartość $_COOKIE['lang'] dla danej przeglądarki
  • lista języków w HTTP Accept-Language (1), (2) header

Najpierw musisz dopasować zapytanie do jednego ze zdefiniowanych wzorców trasowania(jeśli twoim wyborem jest Laravel, to przeczytaj tutaj). Po pomyślnym dopasowaniu wzoru musisz znaleźć język.

Musisz przejść przez wszystkie segmenty wzoru. Znajdź potencjalne tłumaczenia dla wszystkich tych segmentów i ustal, który język został użyty. Dwa dodatkowe źródła (plik cookie i nagłówek) będzie używany do rozwiązywania konfliktów routingu, kiedy (nie "jeśli") one powstają.

Weźmy na przykład: http://site.tld/blog/novinka.

To Transliteracja "блог, новинка", która w języku angielskim oznacza około "blog", "latest".

Jak już można zauważyć, w języku rosyjskim "блог" będzie transliterowane jako "blog". Co oznacza, że w pierwszej części [:query] ty (w w najlepszym przypadku ) skończysz z ['en', 'ru'] listą możliwych języków. Następnie bierzesz następny segment - "novinka". To może mieć tylko jeden język na liście możliwości: ['ru'].

Gdy lista zawiera jeden element, pomyślnie znalazłeś język.

Ale jeśli skończysz z 2 (przykład: rosyjski i ukraiński) lub więcej możliwości .. lub 0 możliwości, w zależności od przypadku. Musisz użyć pliku cookie i / lub nagłówka, aby znaleźć właściwą opcję.

A jeśli Wszystko inne zawiedzie, wybierasz domyślny język strony.

Język jako parametr

Alternatywą jest użycie adresu URL, który może być zdefiniowana jako http://site.tld/[:language]/[:query]. W tym przypadku, tłumacząc zapytanie, nie musisz odgadywać języka, ponieważ w tym momencie już wiesz, którego użyć.

Istnieje również drugorzędne źródło języka: wartość pliku cookie. Ale tutaj nie ma sensu mieszać z nagłówkiem Accept-Language, ponieważ nie masz do czynienia z nieznaną ilością możliwych języków w przypadku "zimnego startu" (gdy użytkownik po raz pierwszy otwiera stronę z niestandardowym zapytaniem).

Zamiast tego masz 3 proste, priorytetowe opcje:

  1. jeśli [:language] segment jest ustawiony, użyj go
  2. jeśli $_COOKIE['lang'] jest ustawione, użyj go
  3. Użyj domyślnego języka

Gdy masz język, po prostu próbujesz przetłumaczyć zapytanie, a jeśli tłumaczenie się nie powiedzie, Użyj "domyślnej wartości" dla tego konkretnego segmentu (na podstawie wyników routingu).

Czy tu nie ma trzeciej opcji?

Tak, technicznie można połączyć oba podejścia, ale to skomplikowałoby proces i tylko pomieścić ludzi którzy chcą ręcznie zmienić adres URL http://site.tld/en/news na http://site.tld/de/news i oczekiwać, że strona z wiadomościami zmieni się na niemiecki.

Ale nawet ten przypadek może być złagodzony za pomocą wartości cookie (która zawierałaby informacje o poprzednim wyborze języka), aby zaimplementować z mniejszą magią i nadzieją.

Jakie podejście zastosować?

Jak już można się domyślić, polecam http://site.tld/[:language]/[:query] jako bardziej rozsądną opcję.

Również w realnej sytuacji word miałbyś 3. główną część w URL: "tytuł". Jak w nazwie produktu w sklepie internetowym lub nagłówku artykułu w serwisie.

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

W tym przypadku '/news/article/121415' będzie kwerendą, a 'EU-as-global-reserve-currency' będzie title. Wyłącznie do celów SEO.

Czy można to zrobić w Laravel?

Trochę, ale nie domyślnie.

Nie znam go zbyt dobrze, ale z tego, co widziałem, Laravel używa prostego mechanizmu routingu opartego na wzorach. Aby zaimplementować wielojęzyczne adresy URL, prawdopodobnie będziesz musiał rozszerzyć Core class (es) , ponieważ routing wielojęzyczny wymaga dostępu do różnych form przechowywania (bazy danych, pamięci podręcznej i / lub plików konfiguracyjnych).

Jest przekierowany. Co teraz?

W wyniku tego wszystkiego otrzymałbyś dwie cenne informacje: bieżący język i przetłumaczone segmenty zapytań. Wartości te mogą być następnie użyte do wysłania do Klasy (klas), która wytworzy wynik.

Zasadniczo następujący URL: http://site.tld/ru/blog/novinka (lub wersja bez '/ru') otrzymuje zamienił się w coś w rodzaju

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

Którego używasz tylko do wysyłania:

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

.. lub jakąś jego odmianę, w zależności od konkretnej realizacji.

 99
Author: tereško,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-17 16:53:11

Implementacja i18n bez osiągów przy użyciu Pre-procesora, zgodnie z sugestią Thomasa Bley ' a.]}

W Pracy niedawno przeszliśmy przez implementację i18n na kilku naszych obiektach, a jedną z rzeczy, z którymi zmagaliśmy się, był hit wydajności radzenia sobie z tłumaczeniem w locie, a następnie odkryłem[27]}ten świetny post na blogu Thomasa Bleya , który zainspirował sposób, w jaki używamy i18n do obsługi dużych obciążeń ruchu przy minimalnej wydajności problemy.

Zamiast wywoływać funkcje dla każdej operacji tłumaczenia, które jak wiemy w PHP jest drogie, definiujemy nasze pliki bazowe za pomocą elementów zastępczych, a następnie używamy pre-procesora do buforowania tych plików (przechowujemy czas modyfikacji pliku, aby upewnić się, że serwujemy najnowszą zawartość przez cały czas).

Tagi Tłumaczenia

Thomas używa tagów {tr} i {/tr}, aby określić, gdzie zaczyna się i kończy tłumaczenie. Ze względu na fakt, że używamy TWIG, nie chcemy używać { aby uniknąć nieporozumień, używamy zamiast tego [%tr%] i [%/tr%]. W zasadzie wygląda to tak:

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

Zauważ, że Thomas sugeruje użycie podstawowego języka angielskiego w pliku. Nie robimy tego, ponieważ nie chcemy modyfikować wszystkich plików tłumaczeniowych, jeśli zmienimy wartość w języku angielskim.

Pliki INI

Następnie tworzymy plik INI dla każdego języka, w formacie 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)

Byłoby banalne, aby umożliwić użytkownikowi modyfikowanie tych wewnątrz CMS, po prostu dostać klawiatury przez preg_split na \n lub = i dzięki czemu CMS może zapisywać do plików INI.

Komponent Przed Procesorem

Zasadniczo, Thomas sugeruje użycie just-In-time 'kompilatora' (choć, w rzeczywistości, jest to preprocesor) funkcji takiej jak ta do pobierania plików tłumaczeń i tworzenia statycznych plików PHP na dysku. W ten sposób zasadniczo buforujemy nasze przetłumaczone pliki zamiast wywoływać funkcję tłumaczenia dla każdego ciągu znaków w pliku:

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

Uwaga: nie sprawdź, czy regex działa, nie skopiowałem go z naszego firmowego serwera, ale możesz zobaczyć, jak działa operacja.

Jak to nazwać

Ten przykład pochodzi od Thomasa Bley ' a, nie ode mnie:]}
// 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();

Przechowujemy język w pliku cookie (lub zmiennej sesji, jeśli nie możemy uzyskać pliku cookie), a następnie pobieramy go na każde żądanie. Możesz połączyć to z opcjonalnym parametrem $_GET, aby nadpisać język, ale nie sugeruję subdomeny-per-language ani strony-per-language ponieważ utrudni to sprawdzenie, które strony są popularne i zmniejszy wartość linków przychodzących, ponieważ będą one trudniej rozprzestrzeniać się.

Dlaczego używać tej metody?

Podoba nam się ta metoda wstępnego przetwarzania z trzech powodów:]}
  1. ogromny zysk wydajności z nie wywołania całej gamy funkcji dla treści, które rzadko się zmieniają (z tym systemem, 100k odwiedzających w języku francuskim nadal kończy się tylko uruchomiony zamiennik tłumaczenia raz).
  2. to nie dodaj dowolne obciążenie do naszej bazy danych, ponieważ używa prostych plików płaskich i jest czystym rozwiązaniem PHP.
  3. możliwość używania wyrażeń PHP w naszych tłumaczeniach.

Tłumaczenie Zawartości Bazy Danych

Po prostu dodajemy kolumnę dla zawartości w naszej bazie o nazwie language, następnie używamy metody accessor dla stałej LANG, którą zdefiniowaliśmy wcześniej, więc nasze wywołania SQL (używając niestety ZF1) wyglądają tak:

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

Nasze artykuły mają złożony klucz główny nad id i language tak więc artykuł {[20] } może istnieć we wszystkich językach. Nasze LANG domyślnie en_US, jeśli nie podano.

URL Slug Translation

Połączyłbym tutaj dwie rzeczy, jedną z nich jest funkcja w Twoim bootstrap, która akceptuje parametr $_GET dla języka i nadpisuje zmienną cookie, a drugą jest routing, który akceptuje wiele ślimaków. Następnie możesz zrobić coś takiego w swoim routingu:

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

Mogą być przechowywane w płaskim pliku, który można łatwo zapisać z panelu administracyjnego. JSON lub XML może zapewnić dobrą strukturę do ich obsługi.

Uwagi Dotyczące Kilku Innych Opcji

PHP-based On-the-Fly Translation

Nie widzę, aby oferowały one jakąkolwiek przewagę nad wstępnie przetworzonymi tłumaczeniami.

Front-end Based Translations

Od dawna uważam te interesujące, ale jest kilka zastrzeżeń. Na przykład musisz udostępnić użytkownikowi całą listę fraz na Twoja witryna, którą planujesz przetłumaczyć, może to być problematyczne, jeśli istnieją obszary witryny, które ukrywasz lub nie zezwoliłeś im na dostęp do nich.

Musisz również założyć, że wszyscy Twoi użytkownicy są chętni i zdolni do korzystania z Javascript na twojej stronie, ale z moich statystyk wynika, że około 2.5% naszych użytkowników działa bez niego(lub używa Noscript, aby zablokować nasze strony z niego).

Tłumaczenia Oparte Na Bazie Danych

Szybkość połączenia bazy danych PHP nie ma o czym pisać, a to dodaje już wysoki narzut wywołania funkcji na każdej frazie do tłumaczenia. Problemy z wydajnością i skalowalnością wydają się przytłaczające dzięki temu podejściu.

 47
Author: Glitch Desire,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-17 12:44:04

Proponuję nie wymyślać koła i używać listy skrótów języków gettext i ISO. Czy widziałeś, jak i18n/l10n zaimplementowano w popularnych Cmsach lub frameworkach?

Używając gettext będziesz miał potężne narzędzie, w którym wiele przypadków jest już zaimplementowanych jak liczby mnogie. W języku angielskim masz tylko 2 opcje: liczby pojedynczej i mnogiej. Ale na przykład w języku rosyjskim istnieją 3 formy i nie jest tak proste jak w języku angielskim.

Również wielu tłumaczy ma już doświadczenie w pracy z gettext.

Spójrz na CakePHP lub Drupal . Oba wielojęzyczne włączone. CakePHP jako przykład lokalizacji interfejsu i Drupal jako przykład tłumaczenia treści.

Dla l10n korzystanie z bazy danych nie jest w ogóle. Będzie mnóstwo zapytań. Standardowym podejściem jest zapisanie wszystkich danych l10n w pamięci na wczesnym etapie (lub podczas pierwszego wywołania funkcji i10n, jeśli wolisz leniwe Ładowanie). To może być odczyt z .plik po lub z DB wszystkie dane naraz. A nie tylko czytać żądane Ciągi z tablicy.

Jeśli potrzebujesz zaimplementować narzędzie online do tłumaczenia interfejsu, możesz mieć wszystkie dane w DB, ale nadal zapisać wszystkie dane do pliku, aby z nim pracować. Aby zmniejszyć ilość danych w pamięci można podzielić wszystkie przetłumaczone wiadomości / ciągi na grupy, a następnie załadować tylko te grupy, których potrzebujesz, jeśli będzie to możliwe.

Więc masz całkowitą rację w swoim #3. Z jednym wyjątkiem: zwykle jest to jeden duży plik, a nie plik dla każdego kontrolera. Ponieważ jest to najlepsze dla wydajność, aby otworzyć jeden plik. Prawdopodobnie wiesz, że niektóre wysokowydajne aplikacje internetowe kompilują cały kod PHP w jednym pliku, aby uniknąć operacji na plikach podczas wywoływania include / require.

O Adresach URL. Google pośrednio sugeruje użycie tłumaczenia:

Aby wyraźnie wskazać Francuskie treści: http://example.ca/fr/vélo-de-montagne.html

Również myślę, że należy przekierować użytkownika do prefiksu domyślnego języka np. http://examlpe.com/about-us will przekierowania do http://examlpe.com/en/about-us Ale jeśli witryna używa tylko jednego języka, więc nie potrzebujesz prefiksów w ogóle.

Zobacz: 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

Tłumaczenie treści jest trudniejszym zadaniem. Myślę, że to będą pewne różnice z różnymi rodzajami treści np. artykuły, pozycje menu itp. Ale w #4 jesteś we właściwym kierunku. Zajrzyj do Drupala, aby mieć więcej pomysłów. Ma wystarczająco jasny schemat DB i wystarczająco dobry interfejs do tłumaczenia. Jak tworzenie artykułu i wybierz język dla niego. A potem można przetłumaczyć go na inne języki.

Drupal translation interface

Myślę, że to nie jest problem z Url ślimaki. Możesz po prostu utworzyć oddzielną tabelę dla ślimaków i będzie to właściwa decyzja. Również przy użyciu odpowiednich indeksów nie ma problemu z zapytaniem tabeli nawet z ogromną ilością danych. I nie było to wyszukiwanie pełnotekstowe, ale dopasowanie ciągów, jeśli użyje typu danych varchar dla slug i możesz mieć indeks na tym polu.

PS Sorry, ale mój angielski jest daleki od ideału.

 12
Author: Yaroslav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-11 03:05:14

To zależy od tego, ile treści ma Twoja strona internetowa. Na początku korzystałem z bazy danych, jak wszyscy inni tutaj, ale skryptowanie wszystkich operacji bazy danych może być czasochłonne. Nie mówię, że jest to idealna metoda, a zwłaszcza jeśli masz dużo tekstu, ale jeśli chcesz to zrobić szybko bez użycia bazy danych, ta metoda może działać, choć nie możesz pozwolić użytkownikom na wprowadzanie danych, które będą używane jako pliki tłumaczeń. Ale jeśli dodasz tłumaczenia samodzielnie, będzie praca:

Powiedzmy, że masz ten tekst:

Welcome!

Możesz wprowadzić to do bazy danych z tłumaczeniami, ale możesz również to zrobić:

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

Teraz, jeśli Twoja strona używa plików cookie, masz to na przykład:

$_COOKIE['language'];

Aby to ułatwić, przekształćmy go w kod, który może być łatwo użyty:

$language=$_COOKIE['language'];

Jeśli twoim językiem cookie jest Walijski i masz ten fragment kodu:

echo $welcome[$language];

Wynikiem tego będzie:

Croeso!

Jeśli potrzebujesz dodać dużo tłumaczenia dla Twojej strony internetowej i bazy danych jest zbyt czasochłonne, za pomocą tablicy może być idealnym rozwiązaniem.

 8
Author: user3749746,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-29 23:35:47

Sugeruję, aby naprawdę nie zależeć od bazy danych do tłumaczenia może to być naprawdę bałagan zadanie i może być skrajnym problemem w przypadku kodowania danych.

Miałem podobny problem przed chwilą i napisałem po zajęciach, aby rozwiązać mój problem

Object: Locale\Locale

<?php

  namespace Locale;

  class Locale{

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

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

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

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

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

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

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

    return $this;
}

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

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

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

    return $this;
}

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

    return false;
}

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

    return array();
}

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

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

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

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

    $this->init_locale();
}

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

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

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

        if ($country) {

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

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

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

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

            foreach ($languages as $lang) {

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

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

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

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

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

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

                    return;
                }
            }
        }
    }

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

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

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

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

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

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

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

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

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

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

    return $translation;
  }
}

Użycie

 <?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');

Jak to działa

{a:1} zastępuje pierwszy argument przekazany do metody Locale::translate('key_name','arg1') {a:2} zastępuje się drugim argumentem przekazanym do metody Locale::translate('key_name','arg1','arg2')

Jak prace detekcyjne

    Po zainstalowaniu geoip zwróci kod kraju przez geoip_country_code_by_name, a jeśli geoip nie jest zainstalowany, powróci do nagłówka HTTP_ACCEPT_LANGUAGE
 5
Author: Shushant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-16 07:15:42

Just a sub answer: Bezwzględnie używaj przetłumaczonych adresów URL z identyfikatorem języka przed nimi: http://www.domain.com/nl/over-ons
Rozwiązania hybrydowe Zwykle się komplikują, więc trzymałbym się tego. Dlaczego? Ponieważ adres url jest niezbędny dla SEO.

O tłumaczeniu db: czy liczba języków jest mniej lub bardziej stała? Czy raczej nieprzewidywalny i dynamiczny? Jeśli to jest stałe, chciałbym po prostu dodać nowe kolumny, w przeciwnym razie przejść z wielu tabel.

Ale ogólnie, dlaczego nie używać Drupal? Wiem, że każdy chce zbudować własny CMS, bo jest szybszy, szczuplejszy itp. itd. Ale to naprawdę zły pomysł!

 4
Author: Remy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-09 07:08:56

Miałem ten sam probem jakiś czas temu, zanim zacząłem używać frameworka Symfony .

  1. Wystarczy użyć funkcji _ _ (), która ma arameters pageId( lub objectId, objectTable opisane w #2), język docelowy i opcjonalny parametr fallback (domyślny) język. Domyślny język może być ustawiony w jakimś globalnym config, aby mieć łatwiejszy sposób, aby zmienić go później.

  2. Do przechowywania zawartości w bazie danych użyłem następującej struktury: (pageId, language, treść, zmienne).

    • PageId będzie FK na twojej stronie, którą chcesz przetłumaczyć. jeśli masz inne obiekty, takie jak wiadomości, galerie lub cokolwiek innego, po prostu podziel je na 2 pola objectId, objectTable.

    • Język-oczywiście przechowywałby łańcuch języka ISO EN_en, LT_lt, EN_us itp.

    • Treść-tekst, który chcesz przetłumaczyć wraz z symbolami wildcards do zastąpienia zmiennej. Przykład " Hello mr. % % name%%. Saldo Twojego konta wynosi %/ align = "left" / "

    • Zmienne-zmienne zakodowane w json. PHP dostarcza funkcje do szybkiego ich analizowania. Przykład "nazwa: Laurynas, bilans: 15.23".

    • Wspomniałeś też o polu ślimaka. możesz swobodnie dodać go do tej tabeli, aby mieć szybki sposób wyszukiwania.

  3. Połączenia z bazą danych muszą być zredukowane do minimum z buforowaniem tłumaczeń. Musi być przechowywana w tablicy PHP, ponieważ jest to najszybsza struktura w języku PHP. Jak ty sprawi, że to buforowanie zależy od Ciebie. Z mojego doświadczenia powinieneś mieć folder dla każdego obsługiwanego języka i tablicę dla każdego pageId. Pamięć podręczna powinna zostać odbudowana po zaktualizowaniu tłumaczenia. Tylko zmieniona tablica powinna być zregenerowana.

  4. Myślę, że odpowiedziałem na to w #2

  5. Twój pomysł jest całkowicie logiczny. ten jest dość prosty i myślę, że nie sprawi ci żadnych problemów.

Adresy URL powinny być tłumaczone za pomocą zapisanych w tabela tłumaczeń.

Ostatnie słowa

Zawsze dobrze jest badać najlepsze praktyki, ale nie odkrywać koła na nowo. wystarczy wziąć i używać komponentów ze znanych frameworków i używać ich.

Spójrz na komponent tłumaczenia Symfony . To może być dobra baza kodu dla Ciebie.

 4
Author: Laurynas Mališauskas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-12 10:54:46

Nie zamierzam próbować udoskonalać już udzielonych odpowiedzi. Zamiast tego opowiem ci o tym, jak mój własny framework PHP OOP obsługuje tłumaczenia.

Wewnętrznie, mój framework używa kodów takich jak en, fr, es, cn i tak dalej. Tablica zawiera języki obsługiwane przez stronę internetową: array ('en', 'fr', 'es', 'cn') Kod języka jest przekazywany przez $_GET (Lang=fr)i jeśli nie jest przekazywany lub nie jest poprawny, jest ustawiany na pierwszy język w tablicy. Tak więc w dowolnym momencie podczas wykonywania programu i od samego na początku znany jest obecny język.

Warto zrozumieć rodzaj treści, które należy przetłumaczyć w typowej aplikacji:

1) komunikaty o błędach z klas (lub kodu proceduralnego) 2) komunikaty o błędach z klas (lub kodu proceduralnego) 3) zawartość strony (zwykle przechowywana w bazie danych) 4) ciągi całej witryny (takie jak nazwa witryny) 5) łańcuchy specyficzne dla skryptu

Pierwszy typ jest prosty do zrozumienia. Zasadniczo mówimy o wiadomościach typu " could not połącz się z bazą danych ...". Komunikaty te muszą być wczytane tylko wtedy, gdy wystąpi błąd. Moja klasa menedżera otrzymuje połączenie z innymi klasami i używając informacji przekazanych jako parametry po prostu przechodzi do odpowiedniego folderu klasy i pobiera plik błędu.

Drugi typ komunikatu o błędzie jest bardziej podobny do komunikatów, które otrzymujesz, gdy Walidacja formularza nie powiodła się. ("Nie możesz odejść ... blank" lub "please choose a password with more than 5 characters"). Struny muszą być załadowany przed rozpoczęciem zajęć.Wiem co to jest

Dla rzeczywistej zawartości strony, używam jednej tabeli na język, każda tabela poprzedzona kodem dla języka. Tak więc en_content jest tabelą z zawartością w języku angielskim, es_content jest dla Hiszpanii, cn_content dla Chin, a fr_content to francuski materiał.

Czwarty rodzaj ciągu jest istotny w całej witrynie. Jest to ładowane za pomocą pliku konfiguracyjnego o nazwie używającej kodu języka, czyli en_lang.php, es_lang.php i tak on W globalnym pliku językowym musisz załadować przetłumaczone języki, takie jak array('English','Chinois', 'Spanish','French') w angielskim pliku globalnym i array ('Anglais', 'Chinois', 'Espagnol',' Francais') w pliku francuskim. Więc kiedy wypełniasz listę rozwijaną wyboru języka, jest ona w odpowiednim języku;)

Wreszcie masz ciągi specyficzne dla skryptu. Więc jeśli napiszesz aplikację do gotowania, może to być "twój piekarnik nie był wystarczająco gorący".

W moim zgłoszeniu cykl, globalny plik językowy jest ładowany jako pierwszy. Znajdziesz tam nie tylko globalne ciągi (jak "strona Jacka"), ale także ustawienia dla niektórych klas. Zasadniczo wszystko, co jest zależne od języka lub Kultury. Niektóre łańcuchy zawierają maski dla dat (MMDDYYYY lub DDMMYYYY) lub kody języków ISO. W głównym pliku językowym dołączam ciągi dla poszczególnych klas, ponieważ jest ich tak mało.

Drugi i ostatni plik językowy odczytany z dysku to plik języka skryptu. lang_pl_home_welcome.php jest plikiem językowym dla skryptu home/welcome. Skrypt jest zdefiniowany przez tryb (home) I Akcję (welcome). Każdy skrypt ma swój własny folder z plikami config i lang.

Skrypt pobiera zawartość z bazy danych nazywając tabelę zawartości tak, jak wyjaśniono powyżej.

Jeśli coś pójdzie nie tak, menedżer wie, skąd pobrać plik błędu zależny od języka. Ten plik jest ładowany tylko w przypadku błędu.

Więc wniosek jest oczywisty. Pomyśl o problemach z tłumaczeniem, zanim zaczniesz rozwijać aplikację lub framework. Potrzebujesz również przepływu pracy programistycznej, który zawiera tłumaczenia. Dzięki mojemu frameworkowi rozwijam całą stronę w języku angielskim, a następnie tłumaczę wszystkie odpowiednie pliki.

Tylko szybkie ostatnie słowo na sposób tłumaczenia ciągów są realizowane. Mój framework ma jeden globalny, $manager, który uruchamia usługi dostępne dla każdej innej usługi. Tak więc np. forma usługa pobiera usługę html i używa jej do napisania html. Jedną z usług w moim systemie jest usługa tłumacza. $translator - >set ($service,$code, $ string) ustawia łańcuch znaków dla bieżącego języka. Plik językowy jest listą takich instrukcji. $translator- > get ($service, $ code) pobiera ciąg tłumaczenia. $Code może być liczbowe jak 1 lub Łańcuchowe jak 'no_connection'. Nie może być kolizji między usługami, ponieważ każda z nich ma swoją własną przestrzeń nazw w obszarze danych tłumacza.

Zamieszczam to tutaj w nadziei, że uratuje to komuś zadanie wymyślenia koła na nowo, tak jak musiałem to zrobić kilka lat temu.

 3
Author: JG Estiot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-16 05:03:18

Zadawałem sobie podobne pytania w kółko, a potem zgubiłem się w językach formalnych... ale żeby ci trochę pomóc chciałbym podzielić się kilkoma odkryciami:

Polecam rzucić okiem na zaawansowany CMS

Typo3 dla PHP (wiem, że jest wiele rzeczy, ale to chyba najbardziej dojrzałe)

Plone in Python

Jeśli okaże się, że sieć w 2013 roku powinna działać inaczej, zacznij od zera. To by znaczyło, że stworzyliśmy zespół wysoko wykwalifikowani / doświadczeni ludzie do budowy nowego CMS. Być może chciałbyś rzucić okiem na polimer w tym celu.

Jeśli chodzi o kodowanie i wielojęzyczne strony internetowe / obsługę języka ojczystego, myślę, że każdy programista powinien mieć pojęcie o unicode. Jeśli nie znasz unicode z pewnością zepsujesz swoje dane. Nie idź z tysiącami kodów ISO. Zachowają tylko trochę pamięci. Ale możesz zrobić dosłownie wszystko za pomocą UTF-8 nawet przechowywać chińskie znaki. Ale za to byś trzeba przechowywać 2 lub 4 znaki bajtowe, które sprawiają, że w zasadzie UTF - 16 lub utf-32.

Jeśli chodzi o kodowanie adresów URL, ponownie nie powinieneś mieszać kodowania i mieć świadomość, że przynajmniej dla nazwy domeny istnieją reguły zdefiniowane przez różne lobby, które zapewniają aplikacje takie jak przeglądarka. np. domena może być bardzo podobna jak:

Ьankofamerica.com lub bankofamerica.com samesamebutdifferent;)

Oczywiście potrzebujesz systemu plików do pracy ze wszystkimi kodowaniami. Kolejny plus dla unicode za pomocą systemu plików utf-8.

Jeśli chodzi o tłumaczenia, pomyśl o strukturze dokumentów. np. książkę lub artykuł. Masz specyfikacje docbook, aby zrozumieć te struktury. Ale w HTML chodzi tylko o bloki treści. Więc chcesz mieć tłumaczenie na tym poziomie, również na poziomie strony internetowej lub poziomu domeny. Więc jeśli blok nie istnieje, po prostu go nie ma, jeśli strona internetowa nie istnieje, zostaniesz przekierowany do górnego poziomu nawigacji. Jeśli domena powinna być zupełnie inna w strukturze nawigacji.. to zupełnie inna struktura do zarządzania. Można to zrobić już za pomocą Typo3.

Jeśli chodzi o frameworki, najbardziej dojrzałe, jakie znam, do robienia ogólnych rzeczy takich jak MVC (buzzword naprawdę tego nienawidzę! Jak "performance" jeśli chcesz coś sprzedać, użyj słowa performance i featurerich i sprzedajesz... co do diabła) jest Zend. Dobrze jest wprowadzić standardy dla programistów PHP chaos. Ale typo3 ma również Framework poza CMS. Niedawno został przebudowany i nazywa się teraz flow3. Frameworki oczywiście obejmują abstrakcję bazy danych, szablony i koncepcje buforowania, ale mają indywidualne mocne strony.

Jeśli chodzi o buforowanie... to może być bardzo skomplikowane / wielowarstwowe. W PHP będziesz myśleć o accellerator, opcode, ale także html, httpd, mysql, xml, css, js ... wszelkiego rodzaju pamięci podręczne. Oczywiście niektóre części powinny być buforowane, a dynamiczne takie jak odpowiedzi na bloga nie powinny. niektóre powinny być żądany przez AJAX z wygenerowanymi adresami URL. JSON, hashbangs itd.

Następnie chciałbyś, aby każdy mały komponent na twojej stronie był dostępny lub zarządzany tylko przez niektórych użytkowników , więc koncepcyjnie odgrywa to dużą rolę.

Również chciałbyś zrobić statystyki , Może masz system rozproszony / facebook Facebooka itp. każde oprogramowanie, które ma być zbudowane na Twoim over the top cms ... więc potrzebujesz innego typu baz danych inmemory, bigdata, xml,

Myślę, że na razie wystarczy. Jeśli nie słyszałeś o TYPO3 / plone lub wspomnianych frameworkach, masz wystarczająco dużo do nauki. Na tej ścieżce znajdziesz wiele rozwiązań na pytania, których jeszcze nie zadałeś.

Jeśli wtedy myślisz, pozwala zrobić nowy CMS, ponieważ jego 2013 i php jest o umrzeć i tak, to r zapraszamy do przyłączenia się do jakiejkolwiek innej grupy programistów mam nadzieję, że nie zgubić.

Powodzenia!

I btw. a może ludzie będą nie masz już żadnych stron internetowych w przyszłości? i wszyscy będziemy na google+? Mam nadzieję, że deweloperzy staną się trochę bardziej kreatywni i zrobią coś użytecznego(aby nie zostać przyswojonym przez borgle)

//// edycja /// Tylko mała myśl dla istniejącej aplikacji:

Jeśli posiadasz CMS PHP mysql i chcesz osadzić obsługę multilang. możesz użyć tabeli z kolumną dodatkową dla dowolnego języka lub wstawić tłumaczenie z ID obiektu i ID języka w tym samym table lub utwórz identyczną tabelę dla dowolnego języka i wstaw tam obiekty, a następnie utwórz Unię select, jeśli chcesz, aby wszystkie były wyświetlane. Do bazy danych użyj UTF8 general ci i oczywiście w przedniej/tylnej części użyj utf8 tekst / kodowanie. Użyłem segmentów ścieżki url dla adresów URL w sposób, który już wyjaśniłeś jak

Domain.org/en/about możesz zmapować Lang ID do tabeli treści. w każdym razie musisz mieć mapę parametrów dla swoich adresów URL, więc chcesz zdefiniować parametr, który ma być mapowane ze ścieżki w twoim adresie URL, która byłaby np.

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

Konfiguracja wyszukiwania

Pageid / url

1 / / o / pracownikach/../..

1 | /../ o / pracownikach../../

Mapowanie parametrów do ścieżki 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;
}
/ Align = "left" /

I aby nie zapomnieć, musisz "przepisać" adres url do generowanego pliku php, który w większości przypadków byłby indeks.php

 1
Author: Dr. Dama,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-18 03:26:15

Praca w Bazie Danych:

Tworzenie tabeli języków 'languages':

Pola:

Language_id (primary and auto increamented)

Language_name

Created_at

Created_by

Updated_at

Updated_by

Tworzenie tabeli w bazie danych 'content':

Pola:

Content_id (primary and auto increamented)

Main_content

Header_content

Footer_content

Leftsidebar_content

Rightsidebar_content

Language_id (foreign key: referred to languages table)

Created_at

Created_by

Updated_at

Updated_by

Front End Work:

Gdy użytkownik wybierze dowolny język z listy rozwijanej lub dowolny obszar, Zapisz wybrany język w sesji jak,

$_SESSION ['language']=1;

Teraz pobiera dane z tabeli bazy danych 'content' Na podstawie identyfikatora języka przechowywanego w sesji.

Szczegóły można znaleźć tutaj http://skillrow.com/multilingual-website-in-php-2/

 -1
Author: user3445130,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-04 05:52:50

Jako osoba, która mieszka w Quebecu, gdzie prawie wszystkie strony są francuskie i angielskie... wypróbowałem wiele, jeśli nie Większość wielojęzycznych wtyczek dla WP... jedynym użytecznym rozwiązaniem, które działa nive z całą moją witryną, jest mQtranslate... żyję i umieram z tym !

Https://wordpress.org/plugins/mqtranslate/

 -1
Author: menardmam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-11 17:12:40

Naprawdę prostą opcją, która działa z każdą stroną internetową, na której można wgrać Javascript jest www.multilingualizer.com

Pozwala umieścić cały tekst dla wszystkich języków na jednej stronie, a następnie ukrywa języki, których Użytkownik nie musi widzieć. Działa dobrze.

 -4
Author: Paul Martin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-08 16:40:24

A co z WORDPRESS + MULTI-LANGUAGE SITE BASIS(plugin)? strona będzie miała strukturę:

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

Wtyczka zapewnia interfejs do tłumaczenia wszystkich fraz, z prostą logiką:

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

Wtedy może być wyprowadzony:
echo translate('my_title', LNG); // LNG is auto-detected

P. S. jednak sprawdź, czy wtyczka jest nadal aktywna.

 -4
Author: T.Todua,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-23 23:27:54