diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..335df2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor +composer.lock +.idea diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c8243c8 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "hpolthof/laravel-translations-db", + "description": "A database translations implementation for Laravel 5.", + "license": "GPL2", + "keywords": ["laravel", "translation", "localization", "database"], + "authors": [ + { + "name": "Paul Olthof", + "email": "hpolthof@gmail.com" + } + ], + "require": { + "php": ">=5.4.0", + "illuminate/translation": "5.*", + "illuminate/database": "5.*", + "illuminate/cache": "5.*" + }, + + "autoload": { + "psr-4": { + "Hpolthof\\Translation\\": "src" + } + }, + "extra": { + "branch-alias": { + "dev-master": "0.1.x-dev" + } + } +} diff --git a/database/migrations/2015_06_10_144817_add_translations_table.php b/database/migrations/2015_06_10_144817_add_translations_table.php new file mode 100644 index 0000000..c67f66a --- /dev/null +++ b/database/migrations/2015_06_10_144817_add_translations_table.php @@ -0,0 +1,40 @@ +increments('id'); + $table->string('locale'); + $table->string('group'); + $table->string('name'); + $table->text('value')->nullable(); + $table->timestamp('viewed_at')->nullable(); + $table->timestamps(); + + $table->index(['locale', 'group']); + $table->unique(['locale', 'group', 'name']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('translations'); + } + +} diff --git a/src/DatabaseLoader.php b/src/DatabaseLoader.php new file mode 100644 index 0000000..5904568 --- /dev/null +++ b/src/DatabaseLoader.php @@ -0,0 +1,77 @@ +where('locale', $locale) + ->where('group', $group) + ->lists('value', 'name'); + } + + /** + * Add a new namespace to the loader. + * This function will not be used but is required + * due to the LoaderInterface. + * We'll just leave it here as is. + * + * @param string $namespace + * @param string $hint + * @return void + */ + public function addNamespace($namespace, $hint) {} + + /** + * Adds a new translation to the database or + * updates an existing record if the viewed_at + * updates are allowed. + * + * @param string $locale + * @param string $group + * @param string $name + * @return void + */ + public function addTranslation($locale, $group, $key) + { + if(!\Config::get('app.debug')) return; + + // Extract the real key from the translation. + if (preg_match("/^{$group}\.(.*?)$/sm", $key, $match)) { + $name = $match[1]; + } else { + throw new TranslationException('Could not extract key from translation.'); + } + + $item = \DB::table('translations') + ->where('locale', $locale) + ->where('group', $group) + ->where('name', $name)->first(); + + $data = compact('locale', 'group', 'name'); + $data = array_merge($data, [ + 'viewed_at' => date_create(), + 'updated_at' => date_create(), + ]); + + if($item === null) { + $data = array_merge($data, [ + 'created_at' => date_create(), + ]); + \DB::table('translations')->insert($data); + } else { + \DB::table('translations')->where('id', $item->id)->update($data); + } + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 0000000..fa4430f --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,80 @@ +registerDatabase(); + $this->registerLoader(); + + $this->app->singleton('translator', function($app) + { + $loader = $app['translation.loader']; + $database = $app['translation.database']; + + // When registering the translator component, we'll need to set the default + // locale as well as the fallback locale. So, we'll grab the application + // configuration so we can easily get both of these values from there. + $locale = $app['config']['app.locale']; + + $trans = new Translator($database, $loader, $locale); + + $trans->setFallback($app['config']['app.fallback_locale']); + + return $trans; + }); + } + + public function boot() + { + $this->publishes([ + __DIR__.'/../database/migrations/' => database_path('/migrations') + ], 'migrations'); + } + + /** + * Register the translation line loader. + * + * @return void + */ + protected function registerLoader() + { + $this->app->singleton('translation.loader', function($app) + { + return new FileLoader($app['files'], $app['path.lang']); + }); + } + + protected function registerDatabase() + { + $this->app->singleton('translation.database', function($app) + { + return new DatabaseLoader(); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('translator', 'translation.loader', 'translation.database'); + } + +} diff --git a/src/TranslationException.php b/src/TranslationException.php new file mode 100644 index 0000000..abeab8c --- /dev/null +++ b/src/TranslationException.php @@ -0,0 +1,5 @@ +database = $database; + parent::__construct($loader, $locale); + } + + protected static function isNamespaced($namespace) + { + return !(is_null($namespace) || $namespace == '*'); + } + + /** + * Get the translation for the given key. + * + * @param string $key + * @param array $replace + * @param string $locale + * @return string + */ + public function get($key, array $replace = array(), $locale = null) + { + list($namespace, $group, $item) = $this->parseKey($key); + + // Here we will get the locale that should be used for the language line. If one + // was not passed, we will use the default locales which was given to us when + // the translator was instantiated. Then, we can load the lines and return. + foreach ($this->parseLocale($locale) as $locale) + { + if(!self::isNamespaced($namespace)) { + // Database stuff + $this->database->addTranslation($locale, $group, $key); + } + + $this->load($namespace, $group, $locale); + + $line = $this->getLine( + $namespace, $group, $locale, $item, $replace + ); + + if ( ! is_null($line)) break; + } + + // If the line doesn't exist, we will return back the key which was requested as + // that will be quick to spot in the UI if language keys are wrong or missing + // from the application's language files. Otherwise we can return the line. + if ( ! isset($line)) return $key; + + return $line; + } + + public function load($namespace, $group, $locale) + { + if ($this->isLoaded($namespace, $group, $locale)) return; + + // If a Namespace is give the Filesystem will be used + // otherwise we'll use our database. + // This will allow legacy support. + if(!self::isNamespaced($namespace)) { + // If debug is off then cache the result forever to ensure high performance. + if(!\Config::get('app.debug')) { + $that = $this; + $lines = \Cache::rememberForever('__translations.'.$locale.'.'.$group, function() use ($that, $locale, $group, $namespace) { + return $this->database->load($locale, $group, $namespace); + }); + } else { + $lines = $this->database->load($locale, $group, $namespace); + } + } else { + $lines = $this->loader->load($locale, $group, $namespace); + } + $this->loaded[$namespace][$group][$locale] = $lines; + } +}