Skip to content

Commit

Permalink
Validate translations command
Browse files Browse the repository at this point in the history
  • Loading branch information
Philippe Damen committed Jan 30, 2024
1 parent eed1f3a commit ebaa4c9
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 90 deletions.
62 changes: 0 additions & 62 deletions src/Commands/CheckCommand.php

This file was deleted.

26 changes: 26 additions & 0 deletions src/Commands/CleanupCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace NextApps\PoeditorSync\Commands;

use Illuminate\Console\Command;
use NextApps\PoeditorSync\Translations\TranslationManager;

class CleanupCommand extends Command
{
protected $signature = 'poeditor:cleanup';

protected $description = 'Cleanup unused translations and run update command if translations don\'t exist anymore.';

public function handle() : mixed
{
// remove empty string translations
app(TranslationManager::class)->removeEmptyTranslations();

// run poeditor:update command if translations don't exist anymore + ask confirmation
if(app(TranslationManager::class)->translationsDontExistAnymore()
&& $this->confirm('Some translations don\'t exist anymore. Do you want to run poeditor:update command?')
) {
$this->call('poeditor:update');
}
}
}
4 changes: 4 additions & 0 deletions src/Commands/DownloadCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public function handle() : int

$this->info('All translations have been downloaded!');

$this->info('Running validate command:');

$this->call('poeditor:validate');

return Command::SUCCESS;
}

Expand Down
79 changes: 79 additions & 0 deletions src/Commands/ValidateTranslationsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace NextApps\PoeditorSync\Commands;

use Illuminate\Console\Command;
use NextApps\PoeditorSync\Translations\TranslationManager;

class ValidateTranslationsCommand extends Command
{
protected $signature = 'poeditor:validate';

protected $description = 'Validate that translations have the same parameters and pluralization';

public function handle() : int
{
$stringVariables = collect(config('app.supported_locales'))->map(
fn ($language) => [
$language,
app(TranslationManager::class)->countStringVariables($language)
]
);

$this->info('The following amount of string variables were found per language:');
$this->table(
['Language', 'String variables'],
$stringVariables->toArray()
);

if($stringVariables->max(fn ($item) => $item[1]) !== $stringVariables->min(fn ($item) => $item[1])) {
$this->info('It seems there are some string variables that are not available in other languages.');

$extraStringVariables = app(TranslationManager::class)->getExtraStringVariables();

if($extraStringVariables->isNotEmpty()) {
$this->info('There might be something wrong with the string variables for the following translation keys:');
$this->table(
['Extra string variables'],
$extraStringVariables->map(fn ($item) => [$item])->toArray()
);
}
}

$invalidTranslations = app(TranslationManager::class)->getPossibleInvalidTranslations();

if($invalidTranslations->isNotEmpty()) {
$this->info('It seems there are some translations that could be invalid in some languages.');
$this->table(
['Language', 'Translation key', 'Original', 'Translated', 'Missing'],
$invalidTranslations->map(
fn ($item, $key) => [
$item['locale'],
$item['key'],
$item['original'],
$item['translated'],
$item['missing']->implode(', ')
]
)->toArray()
);
}

$pluralizationErrors = app(TranslationManager::class)->checkPluralization();

if($pluralizationErrors->isNotEmpty()) {
$this->info('There might be something wrong with the pluralization for the following translation key:');
$this->table(
['Translation key'],
$pluralizationErrors->map(
fn ($item, $key) => [
$item,
]
)->toArray()
);
}

$this->info('All checks have been successfully completed.');

return Command::SUCCESS;
}
}
4 changes: 4 additions & 0 deletions src/PoeditorSyncServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
namespace NextApps\PoeditorSync;

use Illuminate\Support\ServiceProvider;
use NextApps\PoeditorSync\Commands\CleanupCommand;
use NextApps\PoeditorSync\Commands\DownloadCommand;
use NextApps\PoeditorSync\Commands\UploadCommand;
use NextApps\PoeditorSync\Commands\ValidateTranslationsCommand;
use NextApps\PoeditorSync\Poeditor\Poeditor;

class PoeditorSyncServiceProvider extends ServiceProvider
Expand All @@ -19,6 +21,8 @@ public function boot() : void
$this->commands([
DownloadCommand::class,
UploadCommand::class,
ValidateTranslationsCommand::class,
CleanupCommand::class
]);
}
}
Expand Down
128 changes: 100 additions & 28 deletions src/Translations/TranslationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,35 +36,107 @@ public function createTranslationFiles(Collection $translations, string $locale)
}
}

public function getPossibleInvalidTranslations(string $toBeCheckedLocale, string $compareToLocale) : array
{
$compareToTranslations = collect($this->getTranslations($compareToLocale))->dot();
$toBecheckedTranslations = collect($this->getTranslations($toBeCheckedLocale))->dot();

$invalidTranslations = $compareToTranslations->filter(
function (string $translation, string $key) use ($toBecheckedTranslations) {
$toBecheckedTranslation = $toBecheckedTranslations->get($key);

return empty($toBecheckedTranslation)
|| (($replacements = $this->getReplacementKeys($translation))
&& $this->getMissingReplacementKeys(
$this->getReplacementKeys($toBecheckedTranslation),
$replacements
)->isNotEmpty()
);
}
)->map(function (string $translation, string $key) use ($toBecheckedTranslations) {
return [
'original' => $translation,
'translated' => $translated = $toBecheckedTranslations->get($key),
'missing' => $this->getMissingReplacementKeys(
$this->getReplacementKeys($translated),
$this->getReplacementKeys($translation)
),
];
public function getPossibleInvalidTranslations() : Collection
{
$fallbackLocale = config('app.fallback_locale');
$appLanguages = collect(config('app.supported_locales'))->reject(fn ($locale) => $locale === $fallbackLocale);

return $appLanguages->map(function ($locale) use ($fallbackLocale){
$toBecheckedTranslations = collect($this->getTranslations($locale))->dot();

return collect($this->getTranslations($fallbackLocale))->dot()->filter(
function (string $translation, string $key) use ($toBecheckedTranslations) {
$toBecheckedTranslation = $toBecheckedTranslations->get($key);

return empty($toBecheckedTranslation)
|| (($replacements = $this->getReplacementKeys($translation))
&& $this->getMissingReplacementKeys(
$this->getReplacementKeys($toBecheckedTranslation),
$replacements
)->isNotEmpty()
);
}
)->map(function (string $translation, string $key) use ($toBecheckedTranslations, $locale) {
return [
'key' => $key,
'locale' => $locale,
'original' => $translation,
'translated' => $translated = $toBecheckedTranslations->get($key),
'missing' => $this->getMissingReplacementKeys(
$this->getReplacementKeys($translated),
$this->getReplacementKeys($translation)
),
];
});
})->flatten(1);
}

public function checkPluralization() : Collection
{
$appLanguages = collect(config('app.supported_locales'));

$stringVariables = [];

$appLanguages->each(function ($locale) use(&$stringVariables){
collect($this->getTranslations($locale))
->dot()
->mapWithKeys(function($translation, $key){
$matched = preg_match_all('/({\d*}*)|(\|)|(\[\d*,(?:\d+|\**)\])/', $translation, $matches);
return [$key => $matched ? $matches[0] : []];
})
->each(function ($matches, $key) use (&$stringVariables, $locale) {
if(!isset($stringVariables[$key])){
$stringVariables[$key] = [];
}

$stringVariables[$key][$locale] = $matches;
});
});

return collect($stringVariables)
->reject(function ($matches) use($appLanguages){
if (collect($matches)->keys()->toArray() !== $appLanguages->toArray()){
return false;
}

return collect($matches)->unique()->count() === 1;
})->keys();
}

public function countStringVariables(string $locale) : int
{
return collect($this->getTranslations($locale))->dot()->sum(function (string $translation) {
return $this->getReplacementKeys($translation)->count();
});
}

public function getExtraStringVariables() : Collection
{
$appLanguages = collect(config('app.supported_locales'));

$stringVariables = [];

$appLanguages->each(function ($locale) use(&$stringVariables){
collect($this->getTranslations($locale))->dot()->filter(function($translation){
return $this->getReplacementKeys($translation)->isNotEmpty();
})->map(function ($translation, $key){
return $this->getReplacementKeys($translation)->map(function ($replacementKey) use ($key) {
return $key . '.' . Str::lower($replacementKey);
});
})
->flatten()
->each(function ($translation) use (&$stringVariables, $locale) {
if(!isset($stringVariables[$translation])){
$stringVariables[$translation] = [];
}

$stringVariables[$translation][] = $locale;
});
});

return $invalidTranslations->toArray();
return collect($stringVariables)->reject(function ($locales) use($appLanguages){
return $locales === $appLanguages->toArray();
})->keys();
}

protected function getReplacementKeys(string $translation) : Collection
Expand All @@ -75,7 +147,7 @@ protected function getReplacementKeys(string $translation) : Collection
protected function getMissingReplacementKeys(Collection $toBeCheckedReplacementKeys, Collection $replacementKeys) : Collection
{
return $replacementKeys->reject(function ($replacementKey) use ($toBeCheckedReplacementKeys) {
$toBeCheckedReplacementKeys->contains(function ($val) use ($replacementKey) {
return $toBeCheckedReplacementKeys->contains(function ($val) use ($replacementKey) {
return Str::lower($val) === Str::lower($replacementKey);
});
});
Expand Down
Loading

0 comments on commit ebaa4c9

Please sign in to comment.