This course is still being released! Check back later for more chapters.

Get Notified About this Course!

We will send you messages regarding this course only
and nothing else, we promise.
You can unsubscribe anytime by emailing us at:
privacy@symfonycasts.com
Login to bookmark this video
Buy Access to Course
27.

Comando de importación de traducciones

|

Share this awesome video!

|

Keep on Learning!

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Antes de abordar el comando de importación, me he dado cuenta de que hay un error en nuestro ObjectTranslator. Ábrelo y echa un vistazo al método translate(). Estamos utilizando un WeakMap, cuya clave es el objeto. El problema aquí es que si traduces un objeto con la configuración regional francesa y, más tarde, traduces el mismo objeto con la configuración regional española, obtendrás de vuelta la versión francesa, porque eso es lo que hay en el WeakMap.

Una solución sería incluir la configuración regional en la clave de la caché, pero WeakMapno admite claves complejas. Podríamos utilizar una estructura anidada: una matriz de WeakMap's, cada una con la clave de la configuración regional. Pero para simplificar las cosas por ahora, vamos a eliminar por completo WeakMap. Creo que nuestro sistema de caché es lo suficientemente robusto como para que no tengamos problemas de rendimiento. Si lo hacemos, siempre podemos reintroducirlo más adelante.

Así pues, elimina la propiedad $translatedObjects y todas las referencias a ella que aparecen a continuación:

// ... lines 1 - 10
final class ObjectTranslator
{
// ... lines 13 - 31
public function translate(object $object, ?string $locale = null, array $options = []): object
{
// ... lines 34 - 39
return new TranslatedObject($object, $this->translationsFor(
// ... lines 41 - 43
));
}
// ... lines 46 - 67
}

El comando Importar

Bien, una vez aclarado esto, vamos a centrarnos en el comando de importación. En la carpeta tutorial/, copia ObjectTranslationImportCommand.phpen el directorio Command/ de nuestro bundle. Si no ves este archivo, puedes copiarlo desde el script que aparece a continuación:

// ... lines 1 - 2
namespace SymfonyCasts\ObjectTranslationBundle\Command;
// ... lines 4 - 12
/**
* @internal
*/
#[AsCommand(
name: 'object-translation:import',
description: 'Imports object translations from a CSV.',
)]
final class ObjectTranslationImportCommand extends Command
{
public function __construct(
private TranslatableMappingManager $mappingManager,
) {
parent::__construct();
}
protected function configure(): void
{
$this
->addArgument('file', InputArgument::REQUIRED, 'The CSV file to import from.')
->addArgument('locale', InputArgument::REQUIRED, 'The locale to import translations for.')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$file = $input->getArgument('file');
$locale = $input->getArgument('locale');
$io = new SymfonyStyle($input, $output);
$io->title('Importing Object Translations');
$fp = fopen($file, 'r');
$io->progressStart();
fgetcsv($fp);
while (($row = fgetcsv($fp)) !== false) {
[$type, $id, $field, $value] = $row;
$this->mappingManager->upsert($type, $id, $locale, $field, $value);
$io->progressAdvance();
}
fclose($fp);
$io->progressFinish();
$io->success(sprintf('Imported "%s"', $file));
return self::SUCCESS;
}
}

Vamos a recorrerlo. Es bastante similar al comando exportar. En configure(), el primer argumento es el archivo CSV a importar, y el segundo argumento es la configuración regional para la que estamos importando.

En execute(), estamos cogiendo el file y el locale del$input, creando el objeto $io, y luego abriendo el archivo pasado como legible. Esta vez, creamos manualmente una barra de progreso en lugar de utilizarprogressIterate(). Iniciamos la barra de progreso y, a continuación, utilizamos fgetcsv para pasar por la primera fila del archivo CSV (las cabeceras) que no queremos importar.

A continuación, hacemos un bucle sobre todas las filas del archivo CSV, expandiéndolas en variables, y luego las pasamos a este método upsert() aún no creado en nuestro gestor de mapeo. Todavía en este bucle, hacemos avanzar la barra de progreso y, finalmente, cerramos el archivo, terminamos la barra de progreso y mostramos un mensaje de éxito.

¡Genial! Vamos a conectarlo. En el archivo services.php de nuestro bundle, copia la definición del comando exportar y pégala a continuación. Corrige la sangría, renombra el id a import_command y cambia la clase a ObjectTranslationImportCommand:

// ... lines 1 - 11
return static function (ContainerConfigurator $container): void {
$container->services()
// ... lines 14 - 44
->set('.symfonycasts.object_translator.import_command', ObjectTranslationImportCommand::class)
->args([
service('.symfonycasts.object_translator.mapping_manager'),
])
->tag('console.command')
// ... lines 50 - 52
;
};

Implementación del método upsert()

Ahora vamos con el método upsert(). De vuelta en nuestro comando, busca la llamada a upsert() y añade el método a TranslatableMappingManager. Establece todos los tipos de parámetros a string y el tipo de retorno a void:

// ... lines 1 - 13
final class TranslatableMappingManager
{
// ... lines 16 - 105
public function upsert(string $type, string $id, string $locale, string $field, string $value): void
{
// ... lines 108 - 128
}
}

Si no estás familiarizado con el término "upsert", es una combinación de "update" e "insert". Significa actualizar un registro existente si existe, o insertar uno nuevo si no existe. Esto es exactamente lo que queremos hacer al importar traducciones.

En primer lugar, coge el "Gestor de objetos" de la clase de traducción del objeto utilizando$om = $this->doctrine->getManagerForClass($this->translationClass):

// ... lines 1 - 13
final class TranslatableMappingManager
{
// ... lines 16 - 105
public function upsert(string $type, string $id, string $locale, string $field, string $value): void
{
$om = $this->doctrine->getManagerForClass($this->translationClass);
// ... lines 109 - 128
}
}

Ahora intenta encontrar una traducción existente:$translation = $om->getRepository($this->translationClass)->findOneBy(). Para los criterios: 'objectType' => $type, 'objectId' => $id,'locale' => $locale, y 'field' => $field.

// ... lines 1 - 13
final class TranslatableMappingManager
{
// ... lines 16 - 105
public function upsert(string $type, string $id, string $locale, string $field, string $value): void
{
// ... lines 108 - 109
$translation = $om->getRepository($this->translationClass)->findOneBy([
'objectType' => $type,
'objectId' => $id,
'locale' => $locale,
'field' => $field,
]);
// ... lines 116 - 128
}
}

Estas 4 propiedades identifican de forma única una traducción:

Si obtenemos una traducción de la base de datos, se trata de una actualización, si no, es una inserción.

Comprueba si esta traducción no existe con if (!$translation). En este caso, tenemos que crear una nueva. Así que instanciamos un objeto traducción:$translation = new ($this->translationClass)():

// ... lines 1 - 13
final class TranslatableMappingManager
{
// ... lines 16 - 105
public function upsert(string $type, string $id, string $locale, string $field, string $value): void
{
// ... lines 108 - 116
if (!$translation) {
$translation = new ($this->translationClass)();
// ... lines 119 - 122
}
// ... lines 124 - 128
}
}

Sí, ¡puedes instanciar una clase con una variable como ésta!

Entra rápidamente en nuestra clase Model/Translation... genial, todas las propiedades son públicas. Así que, de vuelta a nuestro método upsert(), $translation->objectType = $type,$translation->objectId = $id, $translation->locale = $locale, y$translation->field = $field:

// ... lines 1 - 13
final class TranslatableMappingManager
{
// ... lines 16 - 105
public function upsert(string $type, string $id, string $locale, string $field, string $value): void
{
// ... lines 108 - 116
if (!$translation) {
// ... line 118
$translation->objectType = $type;
$translation->objectId = $id;
$translation->locale = $locale;
$translation->field = $field;
}
// ... lines 124 - 128
}
}

A continuación, establece el valor con $translation->value = $value:

// ... lines 1 - 13
final class TranslatableMappingManager
{
// ... lines 16 - 105
public function upsert(string $type, string $id, string $locale, string $field, string $value): void
{
// ... lines 108 - 124
$translation->value = $value;
// ... lines 126 - 128
}
}

Por último, guarda la traducción con $om->persist($translation) y$om->flush():

// ... lines 1 - 13
final class TranslatableMappingManager
{
// ... lines 16 - 105
public function upsert(string $type, string $id, string $locale, string $field, string $value): void
{
// ... lines 108 - 126
$om->persist($translation);
$om->flush();
}
}

La llamada a persistir es necesaria para las traducciones nuevas, pero es seguro llamarla también para las existentes.

De vuelta al comando, el error de método indefinido ha desaparecido.

Probar el comando Importar

¡Hora de probar! Si recuerdas el capítulo anterior, exportamos nuestras traducciones de objetos a este archivo var/export.csv. Tomé este archivo y traduje la columna value al español y al francés utilizando GitHub Copilot. En el directorio tutorial/, encontrarás import.es.csv y import.fr.csv con los valores traducidos. Si no los ves, están enlazados en el script que aparece a continuación.

En el terminal, importa las traducciones al español ejecutando:

symfony console object-translation:import tutorial/import.es.csv es

Genial, 15 traducciones importadas. Ahora las del francés:

symfony console object-translation:import tutorial/import.fr.csv fr

Como hemos hecho cambios en la base de datos, necesitamos actualizar nuestra caché de traducciones. Por suerte, tenemos nuestro comando de calentamiento:

symfony console object-translation:warmup

En el navegador, estamos en la página de inicio en francés y vemos aquí nuestros datos fijos en francés. Actualiza... y... ¡Genial! Todos los artículos están traducidos al francés. Cambiamos al español y... ¡Boom! ¡Artículos en español! Haz clic en un artículo para ver el contenido completo traducido.

Bien, ¡el código inicial del bundle para una versión 1.0 está listo! A continuación, vamos a empezar a trabajar en los metadatos necesarios para lanzar este bundle al mundo.