Repository URL to install this package:
|
Version:
1.0.0 ▾
|
<?php namespace Modules\Core\Services;
use Illuminate\Config\Repository;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Translation\Translator;
use Illuminate\View\Factory;
use Modules\Core\Exceptions\SupportedLocalesNotDefined;
use Modules\Core\Exceptions\UnsupportedLocaleException;
class Localization
{
/**
* Config repository.
*
* @var Repository
*/
protected $configRepository;
/**
* Illuminate view Factory.
*
* @var Factory
*/
protected $view;
/**
* Illuminate translator class.
*
* @var Translator
*/
protected $translator;
/**
* Illuminate router class.
*
* @var Router
*/
protected $router;
/**
* Illuminate request class.
*
* @var Request
*/
protected $request;
/**
* Illuminate request class.
*
* @var Application
*/
protected $app;
/**
* Illuminate request class.
*
* @var string
*/
protected $baseUrl;
/**
* Default locale
*
* @var string
*/
protected $defaultLocale;
/**
* Supported Locales
*
* @var array
*/
protected $supportedLocales;
/**
* Current locale
*
* @var string
*/
protected $currentLocale = false;
/**
* An array that contains all routes that should be translated
*
* @var array
*/
protected $translatedRoutes = array();
/**
* Name of the translation key of the current route, it is used for url translations
*
* @var string
*/
protected $routeName;
/**
* Creates new instance.
* @throws UnsupportedLocaleException
*/
public function __construct()
{
$this->app = app();
$this->configRepository = $this->app['config'];
$this->view = $this->app['view'];
$this->translator = $this->app['translator'];
$this->router = $this->app['router'];
$this->request = $this->app['request'];
// set default locale
$this->defaultLocale = $this->configRepository->get('app.locale');
$supportedLocales = $this->getSupportedLocales();
if (empty($supportedLocales[$this->defaultLocale])) {
throw new UnsupportedLocaleException("Laravel default locale is not in the supportedLocales array.");
}
}
/**
* Return an array of all supported Locales
*
* @throws SupportedLocalesNotDefined
* @return array
*/
public function getSupportedLocales()
{
if (!empty($this->supportedLocales)) {
return $this->supportedLocales;
}
$locales = $this->configRepository->get('localization.supportedLocales');
if (empty($locales) || !is_array($locales)) {
throw new SupportedLocalesNotDefined();
}
$this->supportedLocales = $locales;
return $locales;
}
/**
* Set and return supported locales
*
* @param array $locales Locales that the App supports
*/
public function setSupportedLocales($locales)
{
$this->supportedLocales = $locales;
}
/**
* Set and return current locale
*
* @param string $locale Locale to set the App to (optional)
*
* @return string Returns locale (if route has any) or null (if route does not have a locale)
*/
public function setLocale($locale = null)
{
if (empty($locale) || !is_string($locale)) {
// If the locale has not been passed through the function
// it tries to get it from the first segment of the url
$locale = $this->request->segment(1);
}
$this->currentLocale = $locale;
// if the first segment/locale passed is not valid
// the system would ask which locale have to take
// it could be taken by the browser
// depending on your configuration
if (empty($this->supportedLocales[$locale])) {
// if we reached this point and hideDefaultLocaleInURL is true
// we have to assume we are routing to a defaultLocale route.
$locale = null;
$this->currentLocale = $this->defaultLocale;
// but if hideDefaultLocaleInURL is false, we have
// to retrieve it from the browser...
if (!$this->hideDefaultLocaleInURL()) {
$this->currentLocale = $this->getCurrentLocale();
}
}
$this->app->setLocale($this->currentLocale);
// Regional locale such as de_DE, so formatLocalized works in Carbon
$regional = $this->getCurrentLocaleRegional();
if ($regional) {
setlocale(LC_TIME, $regional . '.UTF-8');
setlocale(LC_MONETARY, $regional . '.UTF-8');
}
return $locale;
}
/**
* Returns the translation key for a given path
*
* @return boolean Returns value of hideDefaultLocaleInURL in config.
*/
public function hideDefaultLocaleInURL()
{
return $this->configRepository->get('localization.hideDefaultLocaleInURL');
}
/**
* Returns current language
*
* @return string current language
*/
public function getCurrentLocale()
{
if ($this->currentLocale) {
return $this->currentLocale;
}
if ($this->useAcceptLanguageHeader()) {
$negotiator = new LanguageNegotiator($this->defaultLocale, $this->getSupportedLocales(), $this->request);
return $negotiator->negotiateLanguage();
}
// or get application default language
return $this->configRepository->get('app.locale');
}
/**
* Returns the translation key for a given path
*
* @return boolean Returns value of useAcceptLanguageHeader in config.
*/
protected function useAcceptLanguageHeader()
{
return $this->configRepository->get('localization.useAcceptLanguageHeader');
}
/**
* Returns current regional
* @return string current regional
*/
public function getCurrentLocaleRegional()
{
// need to check if it exists, since 'regional' has been added
// after version 1.0.11 and existing users will not have it
if (isset($this->supportedLocales[$this->getCurrentLocale()]['regional'])) {
return $this->supportedLocales[$this->getCurrentLocale()]['regional'];
}
return null;
}
/**
* Returns an URL adapted to $locale or current locale
*
* @param string $url URL to adapt. If not passed, the current url would be taken.
* @param string|boolean $locale Locale to adapt, false to remove locale
*
* @throws UnsupportedLocaleException
*
* @return string URL translated
*/
public function localizeURL($url = null, $locale = null)
{
return $this->getLocalizedURL($locale, $url);
}
/**
* Returns an URL adapted to $locale
*
* @throws SupportedLocalesNotDefined
* @throws UnsupportedLocaleException
*
* @param string|boolean $locale Locale to adapt, false to remove locale
* @param string|false $url URL to adapt in the current language. If not passed, the current url would be taken.
* @param array $attributes Attributes to add to the route, if empty, the system would try to extract them from the url.
*
*
* @return string|false URL translated, False if url does not exist
*/
public function getLocalizedURL($locale = null, $url = null, $attributes = array())
{
if ($locale === null) {
$locale = $this->getCurrentLocale();
}
if (!$this->checkLocaleInSupportedLocales($locale)) {
throw new UnsupportedLocaleException('Locale \'' . $locale . '\' is not in the list of supported locales.');
}
if (empty($attributes)) {
$attributes = $this->extractAttributes($url);
}
if (empty($url)) {
if (!empty($this->routeName)) {
return $this->getURLFromRouteNameTranslated($locale, $this->routeName, $attributes);
}
$url = $this->request->fullUrl();
}
if ($locale && $translatedRoute = $this->findTranslatedRouteByUrl($url, $attributes, $this->currentLocale)) {
return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes);
}
$basePath = $this->request->getBaseUrl();
$parsedUrl = parse_url($url);
$urlLocale = $this->getDefaultLocale();
$path = "";
if ($parsedUrl || !empty($parsedUrl['path'])) {
$parsedUrl['path'] = str_replace($basePath, '', '/' . ltrim($parsedUrl['path'], '/'));
$path = $parsedUrl['path'];
foreach (array_keys($this->getSupportedLocales()) as $localeCode) {
$parsedUrl['path'] = preg_replace('%^/?' . $localeCode . '/%', '$1', $parsedUrl['path']);
if ($parsedUrl['path'] !== $path) {
$urlLocale = $localeCode;
break;
}
$parsedUrl['path'] = preg_replace('%^/?' . $localeCode . '$%', '$1', $parsedUrl['path']);
if ($parsedUrl['path'] !== $path) {
$urlLocale = $localeCode;
break;
}
}
}
$parsedUrl['path'] = ltrim($parsedUrl['path'], '/');
if ($translatedRoute = $this->findTranslatedRouteByPath($parsedUrl['path'], $urlLocale)) {
return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes);
}
if (!empty($locale) && ($locale != $this->defaultLocale || !$this->hideDefaultLocaleInURL())) {
$parsedUrl['path'] = $locale . '/' . ltrim($parsedUrl['path'], '/');
}
//Make sure that the pass path is returned with a leading slash only if it come in with one.
if (starts_with($path, '/') === true) {
$parsedUrl['path'] = '/' . $parsedUrl['path'];
}
$parsedUrl['path'] = rtrim($parsedUrl['path'], '/');
return $this->createUrlFromUri($parsedUrl['path']);
}
/**
* Check if Locale exists on the supported locales array
*
* @param string|boolean $locale string|bool Locale to be checked
* @throws SupportedLocalesNotDefined
* @return boolean is the locale supported?
*/
public function checkLocaleInSupportedLocales($locale)
{
$locales = $this->getSupportedLocales();
if ($locale !== false && empty($locales[$locale])) {
return false;
}
return true;
}
/**
* Extract attributes for current url
*
* @param string|null|false $url to extract attributes, if not present, the system will look for attributes in the current call
*
* @return array Array with attributes
*
*/
protected function extractAttributes($url = false)
{
$attributes = [];
if (empty($url)) {
if (!$this->router->current()) {
return [];
}
$attributes = $this->router->current()->parameters();
$response = event('routes.translation', [$attributes]);
if (!empty($response)) {
$response = array_shift($response);
}
if (is_array($response)) {
$attributes = array_merge($attributes, $response);
}
return $attributes;
}
$parse = parse_url($url);
$parse = isset($parse['path']) ? explode("/", $parse['path']) : [];
$url = [];
foreach ($parse as $segment) {
if (!empty($segment)) {
$url[] = $segment;
}
}
foreach ($this->router->getRoutes() as $route) {
$path = $route->getUri();
if (!preg_match("/{[\w]+}/", $path)) {
continue;
}
$path = explode("/", $path);
$i = 0;
$match = true;
foreach ($path as $j => $segment) {
if (isset($url[$i])) {
if ($segment === $url[$i]) {
$i++;
continue;
}
if (preg_match("/{[\w]+}/", $segment)) {
// must-have parameters
$attributeName = preg_replace(["/}/", "/{/", "/\?/"], "", $segment);
$attributes[$attributeName] = $url[$i];
$i++;
continue;
}
if (preg_match("/{[\w]+\?}/", $segment)) {
// optional parameters
if (!isset($path[$j + 1]) || $path[$j + 1] !== $url[$i]) {
// optional parameter taken
$attributeName = preg_replace(["/}/", "/{/", "/\?/"], "", $segment);
$attributes[$attributeName] = $url[$i];
$i++;
continue;
}
}
} else if (!preg_match("/{[\w]+\?}/", $segment)) {
// no optional parameters but no more $url given
// this route does not match the url
$match = false;
break;
}
}
if (isset($url[$i + 1])) {
$match = false;
}
if ($match) {
return $attributes;
}
}
return [];
}
/**
* Returns an URL adapted to the route name and the locale given
*
* @throws SupportedLocalesNotDefined
* @throws UnsupportedLocaleException
*
* @param string|boolean $locale Locale to adapt
* @param string $transKeyName Translation key name of the url to adapt
* @param array $attributes Attributes for the route (only needed if transKeyName needs them)
*
* @return string|false URL translated
*/
public function getURLFromRouteNameTranslated($locale, $transKeyName, $attributes = array())
{
if (!$this->checkLocaleInSupportedLocales($locale)) {
throw new UnsupportedLocaleException('Locale \'' . $locale . '\' is not in the list of supported locales.');
}
if (!is_string($locale)) {
$locale = $this->getDefaultLocale();
}
$route = "";
if (!($locale === $this->defaultLocale && $this->hideDefaultLocaleInURL())) {
$route = '/' . $locale;
}
if (is_string($locale) && $this->translator->has($transKeyName, $locale)) {
$translation = $this->translator->trans($transKeyName, [], "", $locale);
$route .= "/" . $translation;
$route = $this->substituteAttributesInRoute($attributes, $route);
}
if (empty($route)) {
// This locale does not have any key for this route name
return false;
}
return rtrim($this->createUrlFromUri($route));
}
/**
* Returns default locale
*
* @return string
*/
public function getDefaultLocale()
{
return $this->defaultLocale;
}
/**
* Change route attributes for the ones in the $attributes array
*
* @param $attributes array Array of attributes
* @param string $route string route to substitute
* @return string route with attributes changed
*/
protected function substituteAttributesInRoute($attributes, $route)
{
foreach ($attributes as $key => $value) {
$route = str_replace("{" . $key . "}", $value, $route);
$route = str_replace("{" . $key . "?}", $value, $route);
}
// delete empty optional arguments that are not in the $attributes array
$route = preg_replace('/\/{[^)]+\?}/', '', $route);
return $route;
}
/**
* Create an url from the uri
* @param string $uri Uri
*
* @return string Url for the given uri
*/
public function createUrlFromUri($uri)
{
$uri = ltrim($uri, "/");
if (empty($this->baseUrl)) {
return app('url')->to($uri);
}
return $this->baseUrl . $uri;
}
/**
* Returns the translated route for an url and the attributes given and a locale
*
* @throws SupportedLocalesNotDefined
* @throws UnsupportedLocaleException
*
* @param string|false|null $url Url to check if it is a translated route
* @param array $attributes Attributes to check if the url exists in the translated routes array
* @param string $locale Language to check if the url exists
*
* @return string|false Key for translation, false if not exist
*/
protected function findTranslatedRouteByUrl($url, $attributes, $locale)
{
if (empty($url)) {
return false;
}
// check if this url is a translated url
foreach ($this->translatedRoutes as $translatedRoute) {
$routeName = $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes);
if ($this->getNonLocalizedURL($routeName) == $this->getNonLocalizedURL($url)) {
return $translatedRoute;
}
}
return false;
}
/**
* It returns an URL without locale (if it has it)
* Convenience function wrapping getLocalizedURL(false)
*
* @param string|false $url URL to clean, if false, current url would be taken
*
* @return string URL with no locale in path
*/
public function getNonLocalizedURL($url = null)
{
return $this->getLocalizedURL(false, $url);
}
/**
* Returns the translated route for the path and the url given
*
* @param string $path Path to check if it is a translated route
* @param string $urlLocale Language to check if the path exists
*
* @return string|false Key for translation, false if not exist
*/
protected function findTranslatedRouteByPath($path, $urlLocale)
{
// check if this url is a translated url
foreach ($this->translatedRoutes as $translatedRoute) {
if ($this->translator->trans($translatedRoute, [], "", $urlLocale) == rawurldecode($path)) {
return $translatedRoute;
}
}
return false;
}
/**
* Returns current locale name
*
* @return string current locale name
*/
public function getCurrentLocaleName()
{
return $this->supportedLocales[$this->getCurrentLocale()]['name'];
}
/**
* Returns current locale native name
*
* @return string current locale native name
*/
public function getCurrentLocaleNative()
{
return $this->supportedLocales[$this->getCurrentLocale()]['native'];
}
/**
* Returns current locale direction
*
* @return string current locale direction
*/
public function getCurrentLocaleDirection()
{
if (!empty($this->supportedLocales[$this->getCurrentLocale()]['dir'])) {
return $this->supportedLocales[$this->getCurrentLocale()]['dir'];
}
switch ($this->getCurrentLocaleScript()) {
// Other (historic) RTL scripts exist, but this list contains the only ones in current use.
case 'Arab':
case 'Hebr':
case 'Mong':
case 'Tfng':
case 'Thaa':
return 'rtl';
default:
return 'ltr';
}
}
/**
* Returns current locale script
*
* @return string current locale script
*/
public function getCurrentLocaleScript()
{
return $this->supportedLocales[$this->getCurrentLocale()]['script'];
}
/**
* Returns current language's native reading
*
* @return string current language's native reading
*/
public function getCurrentLocaleNativeReading()
{
return $this->supportedLocales[$this->getCurrentLocale()]['native'];
}
/**
* Returns supported languages language key
*
* @return array keys of supported languages
*/
public function getSupportedLanguagesKeys()
{
return array_keys($this->supportedLocales);
}
/**
* Set current route name
* @param string $routeName current route name
*/
public function setRouteName($routeName)
{
$this->routeName = $routeName;
}
/**
* Translate routes and save them to the translated routes array (used in the localize route filter)
*
* @param string $routeName Key of the translated string
*
* @return string Translated string
*/
public function transRoute($routeName)
{
if (!in_array($routeName, $this->translatedRoutes)) {
$this->translatedRoutes[] = $routeName;
}
return $this->translator->trans($routeName);
}
/**
* Returns the translation key for a given path
*
* @param string $path Path to get the key translated
*
* @return string|false Key for translation, false if not exist
*/
public function getRouteNameFromAPath($path)
{
$attributes = $this->extractAttributes($path);
$path = str_replace(url('/'), "", $path);
if ($path[0] !== '/') {
$path = '/' . $path;
}
$path = str_replace('/' . $this->currentLocale . '/', '', $path);
$path = trim($path, "/");
foreach ($this->translatedRoutes as $route) {
if ($this->substituteAttributesInRoute($attributes, $this->translator->trans($route)) === $path) {
return $route;
}
}
return false;
}
/**
* Returns the config repository for this instance
*
* @return Repository Configuration repository
*
*/
public function getConfigRepository()
{
return $this->configRepository;
}
/**
* Sets the base url for the site
* @param string $url Base url for the site
*
*/
public function setBaseUrl($url)
{
if (substr($url, -1) != "/") {
$url .= "/";
}
$this->baseUrl = $url;
}
/**
* Returns translated routes
*
* @return array translated routes
*/
protected function getTranslatedRoutes()
{
return $this->translatedRoutes;
}
/**
* Returns true if the string given is a valid url
*
* @param string $url String to check if it is a valid url
*
* @return boolean Is the string given a valid url?
*/
protected function checkUrl($url)
{
return filter_var($url, FILTER_VALIDATE_URL);
}
}