123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537 |
- <?php
- /**
- * @package Grav\Common\Language
- *
- * @copyright Copyright (C) 2015 - 2019 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Common\Language;
- use Grav\Common\Grav;
- use Grav\Common\Config\Config;
- use Negotiation\AcceptLanguage;
- use Negotiation\LanguageNegotiator;
- class Language
- {
- protected $grav;
- protected $enabled = true;
- /**
- * @var array
- */
- protected $languages = [];
- protected $page_extensions = [];
- protected $fallback_languages = [];
- protected $default;
- protected $active = null;
- /** @var Config $config */
- protected $config;
- protected $http_accept_language;
- protected $lang_in_url = false;
- /**
- * Constructor
- *
- * @param \Grav\Common\Grav $grav
- */
- public function __construct(Grav $grav)
- {
- $this->grav = $grav;
- $this->config = $grav['config'];
- $this->languages = $this->config->get('system.languages.supported', []);
- $this->init();
- }
- /**
- * Initialize the default and enabled languages
- */
- public function init()
- {
- $default = $this->config->get('system.languages.default_lang');
- if (isset($default) && $this->validate($default)) {
- $this->default = $default;
- } else {
- $this->default = reset($this->languages);
- }
- $this->page_extensions = null;
- if (empty($this->languages)) {
- $this->enabled = false;
- }
- }
- /**
- * Ensure that languages are enabled
- *
- * @return bool
- */
- public function enabled()
- {
- return $this->enabled;
- }
- /**
- * Gets the array of supported languages
- *
- * @return array
- */
- public function getLanguages()
- {
- return $this->languages;
- }
- /**
- * Sets the current supported languages manually
- *
- * @param array $langs
- */
- public function setLanguages($langs)
- {
- $this->languages = $langs;
- $this->init();
- }
- /**
- * Gets a pipe-separated string of available languages
- *
- * @return string
- */
- public function getAvailable()
- {
- $languagesArray = $this->languages; //Make local copy
- $languagesArray = array_map(function($value) {
- return preg_quote($value);
- }, $languagesArray);
- sort($languagesArray);
- return implode('|', array_reverse($languagesArray));
- }
- /**
- * Gets language, active if set, else default
- *
- * @return string
- */
- public function getLanguage()
- {
- return $this->active ?: $this->default;
- }
- /**
- * Gets current default language
- *
- * @return mixed
- */
- public function getDefault()
- {
- return $this->default;
- }
- /**
- * Sets default language manually
- *
- * @param string $lang
- *
- * @return bool
- */
- public function setDefault($lang)
- {
- if ($this->validate($lang)) {
- $this->default = $lang;
- return $lang;
- }
- return false;
- }
- /**
- * Gets current active language
- *
- * @return string
- */
- public function getActive()
- {
- return $this->active;
- }
- /**
- * Sets active language manually
- *
- * @param string $lang
- *
- * @return string|bool
- */
- public function setActive($lang)
- {
- if ($this->validate($lang)) {
- $this->active = $lang;
- return $lang;
- }
- return false;
- }
- /**
- * Sets the active language based on the first part of the URL
- *
- * @param string $uri
- *
- * @return string
- */
- public function setActiveFromUri($uri)
- {
- $regex = '/(^\/(' . $this->getAvailable() . '))(?:\/|\?|$)/i';
- // if languages set
- if ($this->enabled()) {
- // Try setting language from prefix of URL (/en/blah/blah).
- if (preg_match($regex, $uri, $matches)) {
- $this->lang_in_url = true;
- $this->active = $matches[2];
- $uri = preg_replace("/\\" . $matches[1] . '/', '', $uri, 1);
- // Store in session if language is different.
- if (isset($this->grav['session']) && $this->grav['session']->isStarted()
- && $this->config->get('system.languages.session_store_active', true)
- && $this->grav['session']->active_language != $this->active
- ) {
- $this->grav['session']->active_language = $this->active;
- }
- } else {
- // Try getting language from the session, else no active.
- if (isset($this->grav['session']) && $this->grav['session']->isStarted() &&
- $this->config->get('system.languages.session_store_active', true)) {
- $this->active = $this->grav['session']->active_language ?: null;
- }
- // if still null, try from http_accept_language header
- if ($this->active === null &&
- $this->config->get('system.languages.http_accept_language') &&
- $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? false) {
- $negotiator = new LanguageNegotiator();
- $best_language = $negotiator->getBest($accept, $this->languages);
- if ($best_language instanceof AcceptLanguage) {
- $this->active = $best_language->getType();
- } else {
- $this->active = $this->getDefault();
- }
- }
- }
- }
- return $uri;
- }
- /**
- * Get a URL prefix based on configuration
- *
- * @param string|null $lang
- * @return string
- */
- public function getLanguageURLPrefix($lang = null)
- {
- // if active lang is not passed in, use current active
- if (!$lang) {
- $lang = $this->getLanguage();
- }
- return $this->isIncludeDefaultLanguage($lang) ? '/' . $lang : '';
- }
- /**
- * Test to see if language is default and language should be included in the URL
- *
- * @param string|null $lang
- * @return bool
- */
- public function isIncludeDefaultLanguage($lang = null)
- {
- // if active lang is not passed in, use current active
- if (!$lang) {
- $lang = $this->getLanguage();
- }
- return !($this->default === $lang && $this->config->get('system.languages.include_default_lang') === false);
- }
- /**
- * Simple getter to tell if a language was found in the URL
- *
- * @return bool
- */
- public function isLanguageInUrl()
- {
- return (bool) $this->lang_in_url;
- }
- /**
- * Gets an array of valid extensions with active first, then fallback extensions
- *
- * @param string|null $file_ext
- *
- * @return array
- */
- public function getFallbackPageExtensions($file_ext = null)
- {
- if (empty($this->page_extensions)) {
- if (!$file_ext) {
- $file_ext = CONTENT_EXT;
- }
- if ($this->enabled()) {
- $valid_lang_extensions = [];
- foreach ($this->languages as $lang) {
- $valid_lang_extensions[] = '.' . $lang . $file_ext;
- }
- if ($this->active) {
- $active_extension = '.' . $this->active . $file_ext;
- $key = \array_search($active_extension, $valid_lang_extensions, true);
- // Default behavior is to find any language other than active
- if ($this->config->get('system.languages.pages_fallback_only')) {
- $slice = \array_slice($valid_lang_extensions, 0, $key+1);
- $valid_lang_extensions = array_reverse($slice);
- } else {
- unset($valid_lang_extensions[$key]);
- array_unshift($valid_lang_extensions, $active_extension);
- }
- }
- $valid_lang_extensions[] = $file_ext;
- $this->page_extensions = $valid_lang_extensions;
- } else {
- $this->page_extensions = (array)$file_ext;
- }
- }
- return $this->page_extensions;
- }
- /**
- * Resets the page_extensions value.
- *
- * Useful to re-initialize the pages and change site language at runtime, example:
- *
- * ```
- * $this->grav['language']->setActive('it');
- * $this->grav['language']->resetFallbackPageExtensions();
- * $this->grav['pages']->init();
- * ```
- */
- public function resetFallbackPageExtensions()
- {
- $this->page_extensions = null;
- }
- /**
- * Gets an array of languages with active first, then fallback languages
- *
- * @return array
- */
- public function getFallbackLanguages()
- {
- if (empty($this->fallback_languages)) {
- if ($this->enabled()) {
- $fallback_languages = $this->languages;
- if ($this->active) {
- $active_extension = $this->active;
- $key = \array_search($active_extension, $fallback_languages, true);
- unset($fallback_languages[$key]);
- array_unshift($fallback_languages, $active_extension);
- }
- $this->fallback_languages = $fallback_languages;
- }
- // always add english in case a translation doesn't exist
- $this->fallback_languages[] = 'en';
- }
- return $this->fallback_languages;
- }
- /**
- * Ensures the language is valid and supported
- *
- * @param string $lang
- *
- * @return bool
- */
- public function validate($lang)
- {
- return \in_array($lang, $this->languages, true);
- }
- /**
- * Translate a key and possibly arguments into a string using current lang and fallbacks
- *
- * @param string|array $args The first argument is the lookup key value
- * Other arguments can be passed and replaced in the translation with sprintf syntax
- * @param array $languages
- * @param bool $array_support
- * @param bool $html_out
- *
- * @return string
- */
- public function translate($args, array $languages = null, $array_support = false, $html_out = false)
- {
- if (\is_array($args)) {
- $lookup = array_shift($args);
- } else {
- $lookup = $args;
- $args = [];
- }
- if ($this->config->get('system.languages.translations', true)) {
- if ($this->enabled() && $lookup) {
- if (empty($languages)) {
- if ($this->config->get('system.languages.translations_fallback', true)) {
- $languages = $this->getFallbackLanguages();
- } else {
- $languages = (array)$this->getLanguage();
- }
- }
- } else {
- $languages = ['en'];
- }
- foreach ((array)$languages as $lang) {
- $translation = $this->getTranslation($lang, $lookup, $array_support);
- if ($translation) {
- if (\count($args) >= 1) {
- return vsprintf($translation, $args);
- }
- return $translation;
- }
- }
- }
- if ($html_out) {
- return '<span class="untranslated">' . $lookup . '</span>';
- }
- return $lookup;
- }
- /**
- * Translate Array
- *
- * @param string $key
- * @param string $index
- * @param array|null $languages
- * @param bool $html_out
- *
- * @return string
- */
- public function translateArray($key, $index, $languages = null, $html_out = false)
- {
- if ($this->config->get('system.languages.translations', true)) {
- if ($this->enabled() && $key) {
- if (empty($languages)) {
- if ($this->config->get('system.languages.translations_fallback', true)) {
- $languages = $this->getFallbackLanguages();
- } else {
- $languages = (array)$this->getDefault();
- }
- }
- } else {
- $languages = ['en'];
- }
- foreach ((array)$languages as $lang) {
- $translation_array = (array)Grav::instance()['languages']->get($lang . '.' . $key, null);
- if ($translation_array && array_key_exists($index, $translation_array)) {
- return $translation_array[$index];
- }
- }
- }
- if ($html_out) {
- return '<span class="untranslated">' . $key . '[' . $index . ']</span>';
- }
- return $key . '[' . $index . ']';
- }
- /**
- * Lookup the translation text for a given lang and key
- *
- * @param string $lang lang code
- * @param string $key key to lookup with
- * @param bool $array_support
- *
- * @return string
- */
- public function getTranslation($lang, $key, $array_support = false)
- {
- $translation = Grav::instance()['languages']->get($lang . '.' . $key, null);
- if (!$array_support && is_array($translation)) {
- return (string)array_shift($translation);
- }
- return $translation;
- }
- /**
- * Get the browser accepted languages
- *
- * @param array $accept_langs
- * @return array
- * @deprecated 1.6 No longer used - using content negotiation.
- */
- public function getBrowserLanguages($accept_langs = [])
- {
- user_error(__CLASS__ . '::' . __FUNCTION__ . '() is deprecated since Grav 1.6, no longer used', E_USER_DEPRECATED);
- if (empty($this->http_accept_language)) {
- if (empty($accept_langs) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
- $accept_langs = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
- } else {
- return $accept_langs;
- }
- $langs = [];
- foreach (explode(',', $accept_langs) as $k => $pref) {
- // split $pref again by ';q='
- // and decorate the language entries by inverted position
- if (false !== ($i = strpos($pref, ';q='))) {
- $langs[substr($pref, 0, $i)] = [(float)substr($pref, $i + 3), -$k];
- } else {
- $langs[$pref] = [1, -$k];
- }
- }
- arsort($langs);
- // no need to undecorate, because we're only interested in the keys
- $this->http_accept_language = array_keys($langs);
- }
- return $this->http_accept_language;
- }
- /**
- * Accessible wrapper to LanguageCodes
- *
- * @param string $code
- * @param string $type
- * @return bool
- */
- public function getLanguageCode($code, $type = 'name')
- {
- return LanguageCodes::get($code, $type);
- }
- }
|