Witryna internetowa zawierająca najlepsze praktyki w wielu językach

179

Zmagam się z tym pytaniem już od kilku miesięcy, ale nie byłem w sytuacji, w której musiałbym wcześniej zbadać wszystkie możliwe opcje. 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 zaktualizować / przeprojektować system zarządzania treścią, którego używam od dłuższego czasu. Jednak czuję, że wielojęzyczność to świetne ulepszenie tego systemu. Wcześniej nie korzystałem z żadnych frameworków, ale zamierzam używać 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 / frameworka.

Co należy przetłumaczyć

Ponieważ system, którego szukam, musi być jak najbardziej przyjazny dla użytkownika, sposób zarządzania tłumaczeniem powinien znajdować się wewnątrz CMS. Nie powinno być potrzeby nawiązywania połączenia FTP w celu modyfikacji plików tłumaczeń lub jakichkolwiek przeanalizowanych szablonów html / php.

Ponadto szukam najłatwiejszego sposobu przetłumaczenia wielu tabel bazy danych, być może bez konieczności tworzenia dodatkowych tabel.

Co ja sobie wymyśliłem

Ponieważ sam szukałem, czytałem i próbowałem rzeczy. Mam kilka opcji. Ale nadal nie czuję, że znalazłem metodę najlepszej praktyki dla tego, czego naprawdę szukam. Właśnie to wymyśliłem, ale ta metoda ma również skutki uboczne.

  1. Parsowane szablony PHP : system szablonów powinien być analizowany przez PHP. W ten sposób mogę wstawić przetłumaczone parametry do HTML bez konieczności otwierania szablonów i ich modyfikowania. Poza tym, przeanalizowane szablony PHP dają mi możliwość posiadania 1 szablonu dla całej witryny zamiast posiadania podfolderu dla każdego języka (który miałem wcześniej). Metodą 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. Oparte na bazie danych : być może nie muszę o tym więcej wspominać. 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 byłby skonstruowany: Szablony / Controller / view.php może struktura ta może sprawić, że największy sens: Controller.View.parameter. Tabela bazy danych miałaby te pola długie z valuepolami. Wewnątrz szablonów moglibyśmy użyć metody sortowania, takiej jak echo __('Controller.View.welcome', array('name', 'Joshua'))i parametr zawiera Welcome, :name. Zatem wynik jest Welcome, Joshua. Wydaje się, że jest to dobry sposób na zrobienie tego, ponieważ parametry takie jak: nazwa są łatwe do zrozumienia przez edytor.
  3. Niskie obciążenie bazy danych : Oczywiście powyższy system spowodowałby duże obciążenie bazy danych, jeśli te ciągi są ładowane w ruchu. Dlatego potrzebowałbym 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. Myślę, że możemy wybrać languages/en_EN/Controller/View.phplub .ini, cokolwiek Ci odpowiada. Być może .ini jest nawet analizowane szybciej. Ten plik powinien zawierać dane w formacie format parameter=value; . Myślę, że jest to najlepszy sposób na zrobienie tego, ponieważ każdy renderowany widok może zawierać własny plik językowy, jeśli istnieje. Parametry językowe należy następnie załadować do określonego widoku, a nie w zakresie globalnym, aby zapobiec wzajemnemu nadpisywaniu 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ń wiadomości / stron / itp. jak najszybciej. Posiadanie dwóch tabel dla każdego modułu (na przykład Newsi News_translations) jest opcją, ale uzyskanie dobrego systemu wymaga dużo pracy. Jedną z rzeczy wymyśliłem jest oparty na data versioningsystemie pisałem: jest jedna nazwa tabeli bazy danych Translations, tabela ta ma unikalną kombinację language, tablenameaprimarykey. Na przykład: en_En / News / 1 (odnosi się do angielskiej wersji pozycji wiadomości z ID = 1). Ale ta metoda ma dwie ogromne wady: po pierwsze ta tabela jest dość długa z dużą ilością danych w bazie danych, a po drugie użycie tej konfiguracji do przeszukiwania tabeli byłoby cholernie trudnym zadaniem. Np. Wyszukanie slug SEO elementu byłoby wyszukiwaniem pełnotekstowym, co jest dość głupie. Ale z drugiej strony: to szybki sposób na bardzo szybkie tworzenie treści do tłumaczenia w każdej tabeli, ale nie sądzę, aby ten zawodowiec przeważał nad oszustami.
  5. Praca nad front- endem : również front-end wymagałby trochę przemyślenia. Oczywiście będziemy przechowywać dostępne języki w bazie danych i (dez) aktywować te, których potrzebujemy. W ten sposób skrypt może wygenerować listę rozwijaną umożliwiającą wybór języka, a zaplecze może automatycznie zdecydować, jakie tłumaczenia można wykonać za pomocą CMS. Wybrany język (np. En_EN) będzie następnie używany podczas pobierania pliku językowego do wyświetlenia lub do uzyskania odpowiedniego tłumaczenia elementu treści na stronie internetowej.

Więc oto one. Moje dotychczasowe pomysły. Nie zawierają jeszcze nawet opcji lokalizacji 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 przydałoby się to na każdym późniejszym etapie rozwoju. Na razie głównym problemem jest jak najlepsze praktyki tłumaczenia treści na stronie internetowej.

Poza wszystkim, co tutaj wyjaśniłem, mam jeszcze jedną rzecz, na którą jeszcze nie zdecydowałem, wygląda na proste pytanie, ale w rzeczywistości przyprawia mnie o ból głowy:

Tłumaczenie adresu URL? Powinniśmy to zrobić czy nie? iw jaki sposób?

Więc… jeśli mam ten adres URL: http://www.domain.com/about-usa moim domyślnym językiem jest angielski. Czy ten adres URL powinien być przetłumaczony na język http://www.domain.com/over-onsniderlandzki jako mój język? A może powinniśmy pójść na prostszą drogę i po prostu zmienić zawartość strony widocznej pod adresem /about. Ostatnia rzecz nie wydaje się poprawną opcją, ponieważ spowodowałoby to wygenerowanie wielu wersji tego samego adresu URL, a indeksowanie treści nie powiedzie się we właściwy sposób.

http://www.domain.com/nl/about-usZamiast tego jest używana inna opcja . To generuje co najmniej unikalny adres URL dla każdej treści. Byłoby to również łatwiejsze do przejścia na inny język, na przykład, http://www.domain.com/en/about-usa podany adres URL jest łatwiejszy do zrozumienia zarówno dla użytkowników Google, jak i użytkowników. Korzystając z tej opcji, co robimy z domyślnymi językami? Czy język domyślny powinien usunąć język wybrany domyślnie? A więc przekierowanie http://www.domain.com/en/about-usdo http://www.domain.com/about-us... Moim zdaniem jest to najlepsze rozwiązanie, ponieważ gdy CMS jest skonfigurowany tylko dla jednego języka, nie ma potrzeby posiadania tej identyfikacji języka w adresie URL.

Trzecia opcja to połączenie obu opcji: użycie -URL ( http://www.domain.com/about-us) „bez identyfikacji języka” dla języka głównego. I użyj adresu URL z przetłumaczonym kodem SEO dla języków podrzędnych: http://www.domain.com/nl/over-ons&http://www.domain.com/de/uber-uns

Mam nadzieję, że moje pytanie sprawi, że twoje głowy pękną, na pewno złamali moje! Pomogło mi to już w rozwiązaniu kwestii jako pytania tutaj. Dał mi możliwość przejrzenia metod, z których korzystałem wcześniej, oraz pomysłu na nadchodzący CMS.

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ć metoda rezerwowa, w której domyślny tekst jest ładowany, gdy nie ma jeszcze dostępnych tłumaczeń. Jeśli brakuje tłumaczenia, należy je wstawić lub ponownie wygenerować plik tłumaczenia.

Joshua - Pendo
źródło
3
a co z gettext? php.net/manual/en/book.gettext.php
Francois Bourgeois

Odpowiedzi:

115

Założenie tematu

Istnieją trzy różne aspekty wielojęzycznej witryny:

  • tłumaczenie interfejsu
  • zadowolony
  • routing adresów URL

Chociaż wszystkie są ze sobą połączone na różne sposoby, z punktu widzenia CMS są zarządzane za pomocą różnych elementów UI i przechowywane w inny sposób. Wydajesz się być pewien swojej implementacji i zrozumienia pierwszych dwóch. Pytanie dotyczyło tego ostatniego aspektu - „Tłumaczenie adresu URL? Powinniśmy to zrobić czy nie? Iw jaki sposób?”

Z czego można zrobić adres URL?

Bardzo ważną rzeczą jest to, aby nie polubić IDN . Zamiast tego preferuj transliterację (także: transkrypcję i latynizację). Chociaż na pierwszy rzut oka IDN wydaje się realną opcją dla międzynarodowych adresów URL, w rzeczywistości nie działa zgodnie z reklamą z dwóch powodów:

  • 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 prawdopodobnie nie będzie zawierała symboli dla tych liter

Właściwie to 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 zeskrobaniem tego rozwiązania. Podejrzewam też, że może to być wektor ataku.

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

Zasadniczo masz dwie możliwości, które można ująć w następujący sposób:

  • http://site.tld/[:query]: gdzie [:query]decyduje o wyborze języka i treści

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

Zapytanie to Α i Ω ..

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

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

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

Najpierw musisz dopasować zapytanie do jednego ze zdefiniowanych wzorców routingu (jeśli wybierzesz Laravel, przeczytaj tutaj ). Po udanym dopasowaniu wzorca musisz znaleźć język.

Musiałbyś przejść przez wszystkie segmenty wzoru. Znajdź potencjalne tłumaczenia dla wszystkich tych segmentów i określ, który język został użyty. Dwa dodatkowe źródła (ciasteczko i nagłówek) byłyby używane do rozwiązywania konfliktów routingu, kiedy (a nie „jeśli”) się pojawią.

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

To jest transliteracja "блог, новинка", która w języku angielskim oznacza w przybliżeniu "blog", "latest".

Jak już zauważyłeś, w języku rosyjskim „блог” będzie transliterowane jako „blog”. Co oznacza, że ​​pierwsza część [:query]Ciebie (w najlepszym przypadku ) zakończy się ['en', 'ru']listą możliwych języków. Następnie bierzesz następny odcinek - „novinka”. To może mieć tylko jeden język na liście możliwości: ['ru'].

Jeśli lista zawiera jedną pozycję, język został pomyślnie znaleziony.

Ale jeśli otrzymasz 2 (na przykład: rosyjski i ukraiński) lub więcej możliwości ... lub 0 możliwości, w zależności od przypadku. Będziesz musiał 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 witryny.

Język jako parametr

Alternatywą jest użycie adresu URL, który można zdefiniować jako http://site.tld/[:language]/[:query]. W tym przypadku podczas tłumaczenia zapytania nie musisz zgadywać języka, ponieważ w tym momencie już wiesz, którego użyć.

Istnieje również dodatkowe źródło języka: wartość pliku cookie. Ale tutaj nie ma sensu majstrować przy nagłówku Accept-Language, ponieważ nie masz do czynienia z nieznaną liczbą możliwych języków w przypadku „zimnego startu” (gdy użytkownik po raz pierwszy otwiera witrynę z niestandardowym zapytaniem).

Zamiast tego masz 3 proste opcje z priorytetami:

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

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

Czy nie ma tu trzeciej opcji?

Tak, technicznie można połączyć oba podejścia, ale to skomplikowałoby proces i byłoby przeznaczone tylko dla osób, które chcą ręcznie zmienić adres URL http://site.tld/en/newsna http://site.tld/de/newsi oczekują, że strona z wiadomościami zmieni się na niemiecki.

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

Które podejście zastosować?

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

Również w prawdziwym przypadku miałbyś trzecią główną część w adresie URL: „tytuł”. Jak nazwa produktu w sklepie internetowym lub nagłówek artykułu w serwisie informacyjnym.

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

W tym przypadku '/news/article/121415'byłoby zapytaniem i 'EU-as-global-reserve-currency'tytułem. Wyłącznie do celów SEO.

Czy można to zrobić w Laravel?

Niby, ale nie domyślnie.

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

Jest rozbity. Co teraz?

W rezultacie otrzymujesz dwie cenne informacje: aktualny język i przetłumaczone segmenty zapytania. Wartości te można następnie wykorzystać do wysłania do klas, które dadzą wynik.

Zasadniczo następujący adres URL: http://site.tld/ru/blog/novinka(lub wersja bez '/ru') zostaje zamieniony na coś takiego jak

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

Którego po prostu używasz do wysyłki:

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

.. lub jego odmiany, w zależności od konkretnej realizacji.

tereško
źródło
1
Dzięki za kolejny wgląd! Bardzo przemyślane! Myślałem o umieszczeniu parametru języka w adresie URL. Wydaje się to po prostu najlepszym sposobem na zidentyfikowanie konkretnego języka, nie tylko dla użytkownika, ale także do celów SEO. W przypadku, gdy użytkownik zmieni / en / news na / de / news, moim pomysłem było na przykład przekierowanie 301 (stałe) do / de / nachrichten. Aby upewnić się, że każdy język ma tylko jeden unikalny adres URL na stronę (ponownie do celów SEO)
Joshua - Pendo,
Coraz trudniej jest wybrać najlepszą odpowiedź, obecnie jest około 3/4 odpowiedzi, które zasługują na przynajmniej część nagrody. W połączeniu stały się solidną odpowiedzią na wszystko, co chciałem razem wyjaśnić :)
Joshua - Pendo
Zaakceptowałem twoją odpowiedź, aby dać ci przynajmniej trochę dodatkowego przedstawiciela za szczegółową odpowiedź, którą podałeś na temat tłumaczenia adresów URL. Wysoko cenione! Jednak nagroda jest przyznawana osobie pod tobą, ponieważ odpowiedział na każdy aspekt mojego pytania w sposób niezależny od platformy.
Joshua - Pendo
52

Implementacja i18n bez wpływu na wydajność przy użyciu preprocesora, zgodnie z sugestią Thomasa Bleya

W pracy niedawno wdrożyliśmy i18n w kilku naszych nieruchomościach, a jedną z rzeczy, z którymi ciągle się borykaliśmy, był hit związany z tłumaczeniem w locie, po czym odkryłem świetny wpis na blogu Thomasa Bleya co zainspirowało sposób, w jaki używamy i18n do obsługi dużego ruchu przy minimalnych problemach z wydajnością.

Zamiast wywoływać funkcje dla każdej operacji tłumaczenia, co jak wiemy w PHP jest drogie, definiujemy nasze pliki podstawowe za pomocą symboli zastępczych, a następnie używamy preprocesora do buforowania tych plików (przechowujemy czas modyfikacji pliku, aby upewnić się, że obsługujemy najnowsze treści przez cały czas).

Tagi tłumaczeń

Thomas używa tagów {tr}i {/tr}, aby określić, gdzie zaczynają się i kończą tłumaczenia. Ponieważ używamy TWIG, nie chcemy używać, {aby uniknąć nieporozumień, więc używamy [%tr%]i [%/tr%]zamiast tego. Zasadniczo wygląda to tak:

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

Zauważ, że Thomas sugeruje użycie w pliku podstawowego angielskiego. Nie robimy tego, ponieważ nie chcemy modyfikować wszystkich plików tłumaczeń, 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 trywialne, gdyby użytkownik mógł modyfikować je w CMS, po prostu uzyskać pary kluczy za pomocą przycisku preg_spliton \nlub =i umożliwić CMS zapisywanie do plików INI.

Składnik preprocesora

Zasadniczo Thomas sugeruje użycie funkcji „kompilatora” just-in-time (chociaż w rzeczywistości jest to preprocesor) w taki sposób, aby pobierać pliki tłumaczeń i tworzyć statyczne pliki 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 sprawdziłem, czy regex działa, nie skopiowałem go z naszego serwera firmowego, ale możesz zobaczyć, jak działa operacja.

Jak to nazwać

Ponownie, ten przykład pochodzi od Thomasa Bleya, 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 $_GETparametrem, aby zastąpić język, ale nie sugeruję subdomeny na język ani strony na język, ponieważ utrudni to sprawdzenie, które strony są popularne i zmniejszy wartość przychodzących linki, ponieważ będą mniej rozpowszechnione.

Dlaczego warto skorzystać z tej metody?

Podoba nam się ta metoda przetwarzania wstępnego z trzech powodów:

  1. Ogromny wzrost wydajności wynikający z braku wywoływania całej gamy funkcji dla treści, które rzadko się zmieniają (w tym systemie 100 tys. Odwiedzających w języku francuskim nadal będzie wykonywać wymianę tłumaczenia tylko raz).
  2. Nie dodaje żadnego obciążenia do naszej bazy danych, ponieważ używa prostych plików płaskich i jest rozwiązaniem czysto PHP.
  3. Możliwość używania wyrażeń PHP w naszych tłumaczeniach.

Pobieranie przetłumaczonej zawartości bazy danych

Po prostu dodajemy kolumnę na zawartość w naszej bazie danych o nazwie language, a następnie używamy metody LANGakcesora dla stałej, którą zdefiniowaliśmy wcześniej, więc nasze wywołania SQL (niestety przy użyciu ZF1) wyglądają następująco:

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

Nasze artykuły mają związek klucz podstawowy nad idi languagetak artykuł 54może istnieć we wszystkich językach. Nasze LANGwartości domyślne to, en_USjeśli nie określono.

Tłumaczenie Slug URL

Połączyłbym tutaj dwie rzeczy, jedna to funkcja w twoim bootstrapie, która akceptuje $_GETparametr dla języka i przesłania zmienną cookie, a druga to routing, który akceptuje wiele informacji o błędach. 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 mogą zapewnić dobrą strukturę do ich obsługi.

Uwagi dotyczące kilku innych opcji

Tłumaczenie w locie oparte na PHP

Nie widzę żadnej przewagi nad tłumaczeniami wstępnie przetworzonymi.

Tłumaczenia front-endowe

Od dawna uważam, że są one interesujące, ale jest kilka zastrzeżeń. Na przykład, musisz udostępnić użytkownikowi całą listę fraz w swojej witrynie, które planujesz przetłumaczyć, może to być problematyczne, jeśli są obszary witryny, które ukrywasz lub do których nie masz dostępu.

Musiałbyś również założyć, że wszyscy twoi użytkownicy chcą i mogą używać Javascript w Twojej witrynie, ale z moich statystyk wynika, że ​​około 2,5% naszych użytkowników działa bez niego (lub używa Noscript do blokowania korzystania z niego przez nasze witryny) .

Tłumaczenia oparte na bazie danych

Szybkość połączeń z bazą danych PHP nie ma o czym pisać, a to zwiększa i tak już wysoki narzut związany z wywoływaniem funkcji do przetłumaczenia na każdej frazie. W przypadku tego podejścia problemy z wydajnością i skalowalnością wydają się przytłaczające.

Glitch Desire
źródło
Widzę, że pomyliłem cię z "Front-end Translations", miałem na myśli sposób na przeanalizowanie przetłumaczonych ciągów znaków na ekranie. Na pewno nie szukam sposobu na przetłumaczenie tego po stronie klienta! Miałem na myśli najłatwiejszy sposób przełączania języków w interfejsie, ale to oczywiście za pomocą pliku cookie lub ustawienia użytkownika :)
Joshua - Pendo
Aha, i przez Database-Driven bardziej dążyłem do metody zarządzania wszystkimi tłumaczeniami, więc moim idealnym rozwiązaniem byłoby zaplecze, które zapisuje tłumaczenia do bazy danych, a następnie funkcję, która generuje komponent przetwarzania wstępnego, który generuje PHP plik. Why?: proste ... Nie chcę, żeby przeszkadzały mi drobne zmiany w tekście, użytkownicy powinni móc to zrobić sami bez użycia edytora kodu i / lub programu ftp :)
Joshua - Pendo
@PENDO Wiem, że nie miałeś na myśli tłumaczeń front-endowych, to był słabo zawoalowany komentarz do użytkownika, który zasugerował frameworki do tłumaczenia front-end przy użyciu JS. ;)
Glitch Desire
@PENDO Zgadzam się, użyłbym backendu, jak sugerowałeś, ale zamiast bazy danych użyłbym pliku płaskiego ze względu na wydajność. Oczywiście, rdzeń sugestia tutaj jest pre-rendering szablony na zmiany, więc można wymieniać .INIpliki z tabeli bazy danych z 3-kolumnową placeholder, replacement, language. Klawisz złożony na placeholderi language. Następnie dodaj kolejne 2 kolumny z tempfile(ścieżka do szablonu) i modified(DATETIME).
Glitch Desire
1
@PENDO Dzięki. Zrezygnowałem z 250 i planuję przyznać teresko nagrodę w ciągu 24 godzin, kiedy pozwoli mi na to strona, ponieważ wybrałeś obie odpowiedzi jako poprawne, i myślę, że podział najlepiej oddałby Twoje intencje.
Glitch Desire
15

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 systemach CMS lub frameworkach?

Korzystając z gettext, uzyskasz potężne narzędzie, w którym wiele przypadków jest już zaimplementowanych, takich jak mnogie formy liczb. W języku angielskim masz tylko 2 opcje: liczbę pojedynczą i mnogą. Ale na przykład w języku rosyjskim są 3 formularze i nie jest to tak proste, jak w języku angielskim.

Wielu tłumaczy ma już doświadczenie w pracy z gettext.

Spójrz na CakePHP lub Drupal . Oba dostępne w wielu językach. CakePHP jako przykład lokalizacji interfejsu i Drupal jako przykład tłumaczenia treści.

Dla l10n użycie bazy danych w ogóle nie ma miejsca. Będzie mnóstwo zapytań. Standardowym podejściem jest pobranie wszystkich danych l10n do pamięci na wczesnym etapie (lub podczas pierwszego wywołania funkcji i10n, jeśli wolisz ładowanie z opóźnieniem). Może odczytywać z pliku .po lub z bazy danych wszystkie dane naraz. I niż tylko odczytać żądane ciągi z tablicy.

Jeśli potrzebujesz zaimplementować narzędzie online do tłumaczenia interfejsu, możesz mieć wszystkie te dane w DB, ale nadal zapisywać wszystkie dane do pliku, aby z nim pracować. Aby zmniejszyć ilość danych w pamięci, możesz 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 rację w swoim # 3. Z jednym wyjątkiem: zwykle jest to jeden duży plik, a nie plik dla każdego kontrolera. Ponieważ dla wydajności najlepiej jest otworzyć jeden plik. Prawdopodobnie wiesz, że niektóre aplikacje internetowe o dużym obciążeniu kompilują cały kod PHP w jednym pliku, aby uniknąć operacji na plikach, gdy wywoływana jest funkcja include / require.

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

aby wyraźnie wskazać zawartość francuską: http://example.ca/fr/vélo-de-montagne.html

Myślę też, że musisz przekierować użytkownika do domyślnego prefiksu języka, np. Http://examlpe.com/about-us przekieruje do http://examlpe.com/en/about-us. Ale jeśli Twoja witryna używa tylko jednego języka, w ogóle nie potrzebują przedrostków.

Sprawdź: 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 będą to pewne różnice w przypadku różnych typów treści, np. Artykułów, pozycji menu itp. Ale w punkcie 4 jesteś we właściwej drodze. Zajrzyj do Drupala, aby mieć więcej pomysłów. Ma wystarczająco przejrzysty schemat bazy danych i wystarczająco dobry interfejs do tłumaczenia. Tak jak ty tworzysz artykuł i wybierasz dla niego język. A potem możesz przetłumaczyć go na inne języki.

Interfejs tłumaczenia Drupala

Myślę, że to nie jest problem z błędami URL. Możesz po prostu stworzyć osobną tabelę dla ślimaków i będzie to słuszna decyzja. Również użycie odpowiednich indeksów nie stanowi problemu z zapytaniem do tabeli nawet przy dużej ilości danych. I nie było to wyszukiwanie pełnotekstowe, ale dopasowanie stringów, jeśli użyje typu danych varchar dla slug i możesz mieć indeks również w tym polu.

PS Przepraszam, ale mój angielski jest daleki od doskonałości.

Jarosław
źródło
Dziękuję za czas poświęcony na odpowiedź na moje pytanie. Twój angielski jest na tyle dobry, żebym zrozumiał! Dam Ci już +1 za Twoje wysiłki!
Joshua - Pendo,
Jarosław, jeszcze raz dziękuję za odpowiedź. Jednak poszedłem z 2 innych odpowiedzi, które były nieco bardziej kompletne i wyjaśnienie metod stosowanych za kodzie zamiast wskazując, że już tam jest.
Joshua - Pendo
2
Nie ma problemu. Rzeczywiście, te odpowiedzi są dla mnie bardziej kompletne i interesujące do przeczytania. Ale mam nadzieję, że moja odpowiedź też Ci się przydała.
Jarosław
12

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

Powiedzmy, że masz ten tekst:

Welcome!

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

$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 witryna korzysta z plików cookie, masz na przykład:

$_COOKIE['language'];

Aby to ułatwić, przekształćmy go w kod, który można łatwo wykorzystać:

$language=$_COOKIE['language'];

Jeśli Twoim językiem plików cookie jest walijski i masz ten fragment kodu:

echo $welcome[$language];

Wynikiem tego będzie:

Croeso!

Jeśli musisz dodać wiele tłumaczeń do swojej witryny, a baza danych jest zbyt obciążająca, użycie tablicy może być idealnym rozwiązaniem.

user3749746
źródło
1
To nie jest blisko odpowiedzi, o którą prosiłem. Poza tym, zamiast mieć wszystkie języki dostępne na każdej stronie, lepiej utwórz pliki, takie jak te, lang.en.phpktóre zostaną uwzględnione i użyj, $lang['welcome']które jest zadeklarowane w każdym pliku.
Joshua - Pendo
7

Zasugeruję, abyś naprawdę nie polegał na bazie danych przy tłumaczeniu, może to być naprawdę kłopotliwe zadanie i może stanowić ekstremalny problem w przypadku kodowania danych.

Miałem podobny problem jakiś czas temu i napisałem kolejne zajęcia, aby rozwiązać mój problem

Obiekt: 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;
  }
}

Stosowanie

 <?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}jest zastępowany pierwszym argumentem przekazanym do metody Locale::translate('key_name','arg1') {a:2}jest zastępowany drugim argumentem przekazanym do metodyLocale::translate('key_name','arg1','arg2')

Jak działa wykrywanie

  • Domyślnie, jeśli geoipjest zainstalowany, zwróci kod kraju do, geoip_country_code_by_namea jeśli geoip nie jest zainstalowany, powrót do HTTP_ACCEPT_LANGUAGEnagłówka
Shushant
źródło
W jaki sposób baza danych byłaby nieuporządkowana? Ze względu na możliwe znaki w różnych językach? Do tej pory posiadam głównie angielskie, francuskie, holenderskie i niemieckie strony internetowe, więc na razie nie stanowi to problemu. Dzięki za odpowiedź, ale ponieważ to tylko część odpowiedzi, nie zdobędzie nagrody.
Joshua - Pendo
cóż, myślę, że twoje pytanie jest pomocne tylko dla ciebie, ale byliby ludzie, którzy rozważaliby użycie języków takich jak hindi, tajski, chiński i arabski (te języki zajmą więcej 1 bajtu, aby przedstawić znaki) w porównaniu z wymaganymi językami. jeśli używasz bazy danych, to utf8_general_cisortowanie jest odpowiednim sposobem na zrobienie tego.
Shushant
Zgadzam się, sam mam tam trochę śladu. Dzięki za zwrócenie uwagi, również wielobitowe postacie są na tyle ważne, że można o nich wspomnieć w tym pytaniu :)
Joshua - Pendo
5

Tylko odpowiedź podrzędna: absolutnie używaj przetłumaczonych adresów URL z identyfikatorem języka przed nimi: http://www.domain.com/nl/over-ons
Rozwiązania hybrydowe są zwykle skomplikowane, więc po prostu się go trzymam. Czemu? Ponieważ adres URL jest niezbędny dla SEO.

O tłumaczeniu db: Czy liczba języków jest mniej więcej ustalona? Czy raczej nieprzewidywalny i dynamiczny? Jeśli zostanie naprawiony, po prostu dodam nowe kolumny, w przeciwnym razie użyję wielu tabel.

Ale ogólnie, dlaczego nie skorzystać z Drupala? Wiem, że każdy chce zbudować swój własny CMS, bo jest szybszy, mniejszy itp. Itd. Ale to naprawdę zły pomysł!

Remy
źródło
1
Dziękuję za odpowiedź. Powód, dla którego nie chcę korzystać z Drupala / Joomla, jest prosty: chcę się upewnić, że znam wszystkie tajniki mojego systemu, usterki, które można wstrzymać, sposób budowania kodu (i ważne: nie buduje go razem 300 programistów) . Mam wystarczająco dużo powodów, by nie wybierać oprogramowania typu open source. Poza tym chcę, aby moja firma była ważnym czynnikiem dla moich klientów, to źle, że mogą udać się do innego dewelopera i zostawić mnie z niczym.
Joshua - Pendo
7
Myślę, że wszystkie te powody są kwestionowane w tonach artykułów. Miejmy nadzieję, że Twoi klienci nie wybiorą dokładnie Ciebie, ponieważ masz zastrzeżony CMS, którego nikt inny nie może obsługiwać. Ale w każdym razie to zupełnie inna dyskusja.
Remy,
1
Rozumiem twój punkt widzenia, nadal wolę system, dla którego znam wszystkie tajniki i nie czuję niczego, aby polegać na kimkolwiek innym, kiedy używam wtyczki.
Joshua - Pendo
1
Poza tym staram się dokumentować swoją pracę wystarczająco dobrze, ponieważ jestem „jednoosobową armią”, ludzie, którzy dla mnie pracują, nie powinni mieć trudności z poznaniem systemu.
Joshua - Pendo
Zły pomysł to wybranie Drupala, a nawet Google mówi, że nie obchodzi ich, czy adres URL jest przetłumaczony, czy nie. Musi jednak zawierać identyfikator ustawień regionalnych.
undefinedman
5

Nie będę próbował zawężać odpowiedzi już udzielonych. Zamiast tego opowiem o tym, w jaki sposób mój własny framework OOP PHP 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 witrynę: tablica ('en', 'fr', 'es', 'cn') Kod języka jest przekazywany za pośrednictwem $ _GET (lang = fr) i jeśli nie jest przekazywany lub nie jest ważny, to jest ustawiony na pierwszy język w tablicy. Dlatego w każdym momencie wykonywania programu i od samego początku znany jest aktualny język.

Warto zrozumieć, jakie treści należy przetłumaczyć w typowej aplikacji:

1) komunikaty o błędach z klas (lub kod proceduralny) 2) komunikaty o błędach z klas (lub kod proceduralny) 3) zawartość strony (zwykle przechowywana w bazie danych) 4) ciągi znaków dla całej witryny (takie jak nazwa witryny) 5) skrypt- określone ciągi

Pierwszy typ jest łatwy do zrozumienia. Zasadniczo mówimy o komunikatach typu „nie można połączyć się z bazą danych…”. Te komunikaty muszą zostać załadowane tylko wtedy, gdy wystąpi błąd. Moja klasa menedżera otrzymuje wywołanie od innych klas i używając informacji przekazanych jako parametry, po prostu przechodzi do odpowiedniego folderu klasy i pobiera plik błędu.

Drugi typ komunikatów o błędach jest bardziej podobny do komunikatów otrzymywanych, gdy weryfikacja formularza się nie powiodła. („Nie możesz zostawić ... pustego” lub „wybierz hasło składające się z więcej niż 5 znaków”). Ciągi muszą zostać załadowane przed uruchomieniem klasy, wiem co to jest

W przypadku rzeczywistej zawartości strony używam jednej tabeli na język, z każdą tabelą poprzedzoną kodem języka. Więc en_content to tabela z zawartością w języku angielskim, es_content to hiszpania, cn_content dla Chin, a fr_content to francuski.

Czwarty rodzaj ciągów ma znaczenie w całej witrynie. Jest to ładowane za pośrednictwem pliku konfiguracyjnego nazwanego przy użyciu kodu języka, czyli en_lang.php, es_lang.php i tak dalej. W globalnym pliku językowym będziesz musiał załadować przetłumaczone języki, takie jak tablica („angielski”, „chiński”, „hiszpański”, „francuski”) w globalnym pliku i tablicy w języku angielskim („angielski”, „chiński”, „ Espagnol ”,„ Francais ”) w pliku francuskim. Więc kiedy wypełnisz listę rozwijaną do wyboru języka, jest on we właściwym języku;)

Wreszcie masz ciągi znaków specyficzne dla skryptu. Jeśli więc napiszesz aplikację do gotowania, może to brzmieć: „Twój piekarnik nie był wystarczająco gorący”.

W moim cyklu aplikacji globalny plik językowy jest ładowany jako pierwszy. Znajdziesz tam nie tylko łańcuchy globalne (takie jak „Witryna Jacka”), ale także ustawienia niektórych klas. Zasadniczo wszystko, co jest zależne od języka lub kultury. Niektóre z łańcuchów zawierają maski dat (MMDDRRRR lub DDMMRRRR) lub kody języków ISO. W głównym pliku językowym umieszczam ciągi znaków dla poszczególnych klas, ponieważ jest ich tak mało.

Drugim i ostatnim plikiem językowym odczytywanym z dysku jest plik języka skryptowego. lang_en_home_welcome.php to plik językowy dla skryptu home / welcome. Skrypt jest definiowany przez tryb (dom) i akcję (powitanie). 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, jak wyjaśniono powyżej.

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

Wniosek jest więc oczywisty. Pomyśl o problemach z tłumaczeniem, zanim zaczniesz tworzyć aplikację lub framework. Potrzebny jest również przepływ pracy programistycznej obejmujący tłumaczenia. W moim frameworku tworzę całą witrynę w języku angielskim, a następnie tłumaczę wszystkie odpowiednie pliki.

Tylko krótkie ostatnie słowo na temat sposobu implementacji ciągów tłumaczeniowych. Mój framework ma jeden globalny menedżer $, który uruchamia usługi dostępne dla każdej innej usługi. Na przykład usługa formularza przechwytuje usługę html i używa jej do napisania kodu HTML. Jedną z usług w moim systemie jest usługa tłumacza. $ translator-> set ($ service, $ code, $ string) ustawia ciąg dla bieżącego języka. Plik językowy jest listą takich oświadczeń. $ translator-> get ($ service, $ code) pobiera ciąg tłumaczenia. Kod $ może mieć postać numeryczną, np. 1, lub ciąg znaków, np. „No_connection”. Nie może być kolizji między usługami, ponieważ każda z nich ma własną przestrzeń nazw w obszarze danych tłumacza.

Publikuję to tutaj w nadziei, że pozwoli to komuś na ponowne wynalezienie koła, tak jak musiałem to zrobić kilka lat temu.

JG Estiot
źródło
4

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

  1. Wystarczy użyć funkcji __ (), która ma parametry pageId (lub objectId, objectTable opisane w # 2), język docelowy i opcjonalny parametr języka rezerwowego (domyślnego). Domyślny język można ustawić w jakiejś globalnej konfiguracji, aby mieć łatwiejszy sposób na późniejszą zmianę.

  2. Do przechowywania treści w bazie danych wykorzystałem następującą strukturę: (pageId, język, treść, zmienne).

    • pageId byłby FK do strony, 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 przechowuje ciąg języka ISO EN_en, LT_lt, EN_us itp.

    • content - tekst, który chcesz przetłumaczyć wraz z symbolami zastępującymi zmienne. Przykład „Witaj panie %% imię %%. Saldo Twojego konta to %% balance %%.”

    • zmienne - zmienne zakodowane w formacie JSON. PHP zapewnia funkcje do szybkiego ich analizowania. Przykład „imię i nazwisko: Laurynas, saldo: 15,23”.

    • wspomniałeś również o polu ślimaka. możesz dowolnie dodać go do tej tabeli, aby szybko go wyszukać.

  3. Twoje wywołania bazy danych muszą być ograniczone do minimum dzięki buforowaniu tłumaczeń. Musi być przechowywany w tablicy PHP, ponieważ jest to najszybsza struktura w języku PHP. To, jak zrobisz to buforowanie, zależy od Ciebie. Z mojego doświadczenia wynika, że ​​powinieneś mieć folder dla każdego obsługiwanego języka i tablicę dla każdego pageId. Po zaktualizowaniu tłumaczenia pamięć podręczna powinna zostać odbudowana. TYLKO zmieniona tablica powinna zostać ponownie wygenerowana.

  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 żadnych problemów.

Adresy URL powinny być tłumaczone przy użyciu zapisanych informacji o błędach w tabeli tłumaczeń.

Ostatnie słowa

zawsze dobrze jest zbadać najlepsze praktyki, ale nie wynajdować koła na nowo. po prostu weź i użyj komponentów z dobrze znanych frameworków i używaj ich.

spójrz na komponent translacyjny Symfony . Może to być dla Ciebie dobra baza kodu.

Laurynas Mališauskas
źródło
Dziękuję za komentarz, a także +1 za poświęcony czas. Laravel (w moim przypadku) używa niektórych części Symfony, jeśli się nie mylę, więc masz absolutną rację, jeśli chodzi o wymyślanie koła na nowo. Zacząłem to pytanie (i nagrodę), aby uzyskać wgląd w sposób, w jaki inni wykonują tłumaczenia, zaczynam wierzyć, że istnieje wiele najlepszych praktyk :-)
Joshua - Pendo
1

W kółko zadawałem sobie podobne pytania, a potem zagubiłem się w formalnych językach ... ale żeby ci trochę pomóc, chciałbym podzielić się kilkoma spostrzeżeniami:

Polecam rzucić okiem na zaawansowany CMS

Typo3bo PHP (wiem, że jest dużo rzeczy, ale myślę, że to jest najbardziej dojrzała)

Plone w Python

Jeśli stwierdzisz, że internet w 2013 roku powinien działać inaczej, zacznij od zera. Oznaczałoby to zebranie zespołu wysoko wykwalifikowanych / doświadczonych ludzi w celu zbudowania nowego CMS. Może zechcesz przyjrzeć się polimerowi w tym celu.

Jeśli chodzi o kodowanie i wielojęzyczne strony internetowe / obsługę języków ojczystych, myślę, że każdy programista powinien mieć wskazówkę dotyczącą Unicode. Jeśli nie znasz Unicode, z pewnością zepsujesz swoje dane. Nie idź z tysiącami kodów ISO. Oszczędzą ci tylko trochę pamięci. Ale możesz zrobić dosłownie wszystko z UTF-8, nawet przechowywać chińskie znaki. Ale w tym celu musiałbyś przechowywać 2 lub 4 bajtowe znaki, co sprawia, że ​​jest to w zasadzie utf-16 lub utf-32.

Jeśli chodzi o kodowanie adresów URL, ponownie nie powinieneś mieszać kodowań i pamiętaj, ż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, na przykład:

ьankofamerica.com lub bankofamerica.com są takie same, ale różne;)

Oczywiście potrzebujesz systemu plików do pracy ze wszystkimi kodowaniami. Kolejny plus dla Unicode używającego systemu plików utf-8.

Jeśli chodzi o tłumaczenia, pomyśl o strukturze dokumentów. np. książka lub artykuł. Masz docbookspecyfikacje do zrozumienia tych struktur. Ale w HTML chodzi tylko o bloki treści. Więc chciałbyś mieć tłumaczenie na tym poziomie, również na poziomie strony internetowej lub domeny. Więc jeśli blok nie istnieje, po prostu go tam nie ma, jeśli strona nie istnieje, zostaniesz przekierowany na wyższy poziom nawigacji. Jeśli domena ma mieć zupełnie inną strukturę nawigacji, to ... jest to zupełnie inna struktura do zarządzania. Można to już zrobić za pomocą Typo3.

Jeśli chodzi o frameworki, najbardziej dojrzałe, jakie znam, do robienia ogólnych rzeczy, takich jak MVC (modne hasło, naprawdę go nienawidzę! Na przykład „wydajność”. Jeśli chcesz coś sprzedać, użyj słowa wydajność i bogactwo funkcji, a sprzedajesz ... co? do diabła) jest Zend. Okazało się, że dobrze jest wprowadzić standardy do koderów chaosu PHP. Ale typo3 ma również Framework oprócz CMS. Niedawno został przebudowany i teraz nazywa się flow3. Ramy obejmują oczywiście abstrakcję bazy danych, tworzenie szablonów i koncepcje buforowania, ale mają indywidualne mocne strony.

Jeśli chodzi o buforowanie ... to może być strasznie skomplikowane / wielowarstwowe. W PHP pomyślisz o accelleratorze, opkodzie, ale także html, httpd, mysql, xml, css, js ... wszelkiego rodzaju pamięciach podręcznych. Oczywiście niektóre części powinny być przechowywane w pamięci podręcznej, a części dynamiczne, takie jak odpowiedzi na blogach, nie powinny. Niektóre powinny być wysyłane przez AJAX z wygenerowanymi adresami URL. JSON, hashbang itp.

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

Chciałbyś również sporządzić statystyki , być może masz system rozproszony / facebook lub facebooki itp. Jakiekolwiek oprogramowanie do zbudowania na twoich over-top cms ... więc potrzebujesz różnych typów baz danych w pamięci, bigdata, xml, cokolwiek .

Cóż, myślę, że na razie wystarczy. Jeśli nie słyszałeś o typo3 / plone lub wspomnianych frameworkach, masz wystarczająco dużo, aby się uczyć. Na tej ścieżce znajdziesz wiele rozwiązań na pytania, których jeszcze nie zadałeś.

Jeśli więc myślisz, stwórzmy nowy CMS, ponieważ jego 2013 i php i tak wkrótce umrą, to możesz dołączyć do innej grupy programistów, miejmy nadzieję, że się nie zgubisz.

Powodzenia!

A tak przy okazji. co powiesz na to, że ludzie nie będą już mieć żadnych witryn internetowych w przyszłości? i wszyscy będziemy w Google +? Mam nadzieję, że programiści staną się trochę bardziej kreatywni i zrobią coś pożytecznego (aby nie zostać przyswojonym przez mieszkańców)

//// Edytuj /// Pomyśl tylko o istniejącej aplikacji:

Jeśli masz php mysql CMS i chcesz osadzić obsługę wielu języków. możesz albo użyć swojej tabeli z dodatkową kolumną dla dowolnego języka, albo wstawić tłumaczenie z identyfikatorem obiektu i identyfikatorem języka w tej samej tabeli lub utworzyć identyczną tabelę dla dowolnego języka i wstawić tam obiekty, a następnie utworzyć sumę wyboru, jeśli chcesz wyświetlić je wszystkie. Do bazy danych użyj ogólnego utf8 ci i oczywiście we front / backend użyj utf8 text / encoding. Użyłem segmentów ścieżki URL dla adresów URL w sposób, który już wyjaśniłeś

domain.org/en/about możesz zmapować identyfikator języka do swojej tabeli treści. w każdym razie musisz mieć mapę parametrów dla swoich adresów URL, więc chciałbyś zdefiniować parametr, który ma być zmapowany z segmentu ścieżek w Twoim adresie URL, np.

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

konfiguracja wyszukiwania

pageid | url

1 | /about/employees/../ ..

1 | /../about/employees../../

odwzoruj parametry na segment ścieżek adresów 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;
}

na przykład, to zostało już omówione w górnym poście.

I żeby nie zapominać, musiałbyś "przepisać" adres URL do generowanego pliku php, którym w większości przypadków byłby index.php

Dr Dama
źródło
Dzięki za komentarz, na pewno są rzeczy, o których powinienem pomyśleć. Używam kodowania utf8 już od kilku lat, kiedyś zmagałem się ze znakami ;-) Z drugiej strony typ CMS / Framework nie miał być czynnikiem w twojej odpowiedzi, ponieważ szukałem metoda niezależna od platformy, tak jakbyśmy kodowali od zera.
Joshua - Pendo
jeśli naprawdę chcesz kodować od zera, polecam przyjrzeć się Dartlangowi i polimerowi. Ponieważ dartlang działa w przeglądarce i ma wsparcie dla 32 i 64 bitów i może być używany do większości celów po stronie serwera i ma kompilator dart2js, naprawdę warto go przestudiować. Jeśli ludzie mówią o niezależności platformy, myślą o Javie ... wiemy, co to oznacza. Proces kompilacji ... Myślę, że użyłbym JSON do wymiany. wygenerowana witryna internetowa z hashbangami i serwerem .. dobrze zrób wszystko, co chcesz, aby zapewnić współpracę.
Dr Dama,
Głównym zadaniem jest układanie i generowanie bazy danych. Nikt nie zrobi tego za Ciebie ... ale liczy się sam Pomysł. Ponieważ nie obchodzą mnie lobby, ale załatwianie spraw, mam nadzieję, że możesz tworzyć modele i udostępniać jakieś rzeczy. Pracuję teraz nad podobnymi zadaniami. Ale nadal planuję. Rozważam Typo3 jako backend i tworzę nową strukturę klienta. Wzorzec wielojęzyczny jest rozwiązany w zapleczu i udostępnia informacje w dedykowany sposób dla wyszukiwarek / usług internetowych. W każdym razie jest to wszystko kontekstualne i ciągłe zadanie budowania
Dr. Dama,
-1

Praca w bazie danych:

Utwórz tabelę języków „języki”:

Pola:

language_id(primary and auto increamented)

language_name

created_at

created_by

updated_at

updated_by

Utwórz tabelę w bazie danych „zawartość”:

Pola:

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

Praca front-end:

Gdy użytkownik wybierze dowolny język z listy rozwijanej lub dowolnego obszaru, zapisz wybrany identyfikator języka w sesji, np.

$_SESSION['language']=1;

Teraz pobierz dane z tabeli bazy danych „zawartość” na podstawie identyfikatora języka przechowywanego w sesji.

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

user3445130
źródło
1
To jest sposób na prostą integrację językową wtedy potrzebną, czy próbowałeś w ogóle przeczytać pełne posty i udzielone odpowiedzi?
Joshua - Pendo
-2

Jako osoba mieszkająca w Quebecu, gdzie prawie cała witryna jest francuska i angielska ... wypróbowałem wiele, jeśli nie większość, wielojęzycznych wtyczek do 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/

menardmam
źródło
1
tak cóż, WP nie było żadnym czynnikiem w pytaniu. To mógł być komentarz aswel
Joshua - Pendo
-3

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

  • przyklad.com/ pl / kategoria1 / ....
  • example.com/ eng / my-page ....
  • example.com/ rus / category1 / ....
  • example.com/ rus / my-page ....

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

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

wtedy można wyprowadzić:
echo translate('my_title', LNG); // LNG is auto-detected

ps jednak sprawdź, czy wtyczka jest nadal aktywna.

T.Todua
źródło
3
a nie „Holla userio” po hiszpańsku to „Hola
Usuario
1
Lol Holla userio, to było zabawne!
spekdrum
z tego powodu, że nie znałem hiszpańskiego (właśnie użyłem przykładu), pośpiesz się, aby głosować przeciw !! :)
T.Todua
-5

Naprawdę prostą opcją, która działa z każdą witryną, na której można przesłać JavaScript, jest www.multilingualizer.com

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

Paul Martin
źródło
Uważaj, SEO byłoby bardzo złe! Dodatkowo ładujesz całą zawartość, podczas gdy potrzebujesz tylko jej części, co jest naprawdę złą praktyką.
Hafenkranich
dziwne rzeczy, że strona jest tylko w języku angielskim ... dlaczego nie używają ich rozwiązania?
eduardo.lopes