Some problems with translation (localization) of forms

Nicolas_K
Member | 25
+
0
-

Hello!
(I hope I'm not getting on your nerves..)

I've found /vendor/nette/forms/examples/localization.php which helped me a lot to understand a bit of the principle of localization in Nette.
But there is no hint in the docs (or I've overseen it?) Proposal: mention that!

Now I have a problem with textareas that do not translate defaultvalue nor placeholder.
And with email that keeps english warning as long as the format doesn't fit.

This is in my FormContactPresenter.php:

class MyTranslator implements Nette\Localization\Translator
{
	// same as in examples, unchanged
}

final class FormContactPresenter extends Nette\Application\UI\Presenter
{
	...

	protected function createComponentMessageForm(): Form
	{
		$form = new Form;
		$translator = new MyTranslator(parse_ini_file('../app/lang/IT.ini'));	// dynamic later..
		$form->setTranslator($translator);

		$form->addTextArea('form_hint', 'Mitteilung:', 40, 3)
		->setDisabled()
		->setDefaultValue("Hier steht was zur Erläuterung")	// This doesn't translate!
		// even in *not* disabled textareas it does not translate
		;

		$form->addText('form_name', 'Name*:')
		->addRule($form::PATTERN, 'Einen normalen Namen bitte', '[- A-zÀ-ÿ\.]{2,40}*')	// This translates
		->setHtmlAttribute('placeholder', 'Name → Pflichtfeld')	// This translates
		->setHtmlAttribute('autocomplete', 'on')
		->setRequired('Dein Name')	// This translates
		;

		$form->addEmail('form_email', 'Email*:')
		->addRule($form::EMAIL, 'Eine gültige Email-Adresse bitte')	// This is ignored - english warning instead!
		->addRule($form::MAX_LENGTH, 'Ahoi!', 40)	// The message is mandatory, although it is never shown
			// because the field refuses to allow input beyond the length
		->addRule($form::PATTERN, 'Eine gültige Email-Adresse bitte', '([a-zA-Z0-9\+_\-]+)(\.[a-zA-Z0-9\+_\-]+)*@([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,10}')	// This translates
		->setHtmlAttribute('placeholder', 'deine@email.adresse → Pflichtfeld')	// This translates
		->setHtmlAttribute('autocomplete', 'on')
		->setRequired('Deine Email-Adresse')	// This translates
		;

I'd be very glad if someone takes a look at this, and perhaps has a solution for me!

m.brecher
Generous Backer | 736
+
0
-

@Nicolas_K

Hi Nicolas from sunny Croatia ;)

I do not much about translation in Nette but some basic knowledges I have.

This I can explain:

$form->addTextArea('form_hint', 'Mitteilung:', 40, 3)
		->setDisabled()
		->setDefaultValue("Hier steht was zur Erläuterung")	// This doesn't translate!
		// even in *not* disabled textareas it does not translate

Nette supports translations via Translator (implemented by developer in latte templates and in forms). But the $defaultValue doesn´t belong to the form but belongs to the data which are as standard located in database. In case that you will use the textarea input for editing for example an article, the default value will be filled with the data from database. And database data must be translated by solution designed by the developer, not by Nette.

Solution:

$form = new Form;

.....

$formHints = ['en' => 'English version ...', 'de' => 'Deutsch version ....']

$form->addTextArea('form_hint', 'Mitteilung:', 40, 3)
		->setDisabled()
		->setDefaultValue($formHints[$lang]);   // $lang - current language version

In the other cases you may use similar solution.

Nicolas_K
Member | 25
+
0
-

Thank you, Miloš! This helps!

m.brecher
Generous Backer | 736
+
0
-

@Nicolas_K

Hi Nicolas from sunny Croatia ;)

I correct my greeting: Hi Nicolas from sunny Switzerland ;), I had a discussion with another Nicolas here who was from Croatia, sorry for my chaos :)

m.brecher
Generous Backer | 736
+
0
-

@Nicolas_K

Iam thinking about this not translated validation rule error message:

$form->addEmail('form_email', 'Email*:')
		->addRule($form::EMAIL, 'Eine gültige Email-Adresse bitte')	// This is ignored - english warning instead!

All validation rule error messages should be translated by the Form translator. The reason why this was not translated is may be a bug in implementation of Email input in Nette Forms. The Email input created with a):

$form->addEmail('form_email', 'Email*:')

has the validation rule:

->addRule($form::EMAIL, ....')

implemented by default, so there is a “potential conflict” between the default email validation and your rule with your error message.

Try to go this way b):

$form->addText('form_email', 'Email*:')
		->setHtmlAttribute('type', 'email')
		->addRule($form::EMAIL, 'Eine gültige Email-Adresse bitte')

or try c) just like standard textbox:

$form->addText('form_email', 'Email*:')
		->setHtmlAttribute('type', 'email')
		->addRule($form::EMAIL, 'Eine gültige Email-Adresse bitte')

because Nette Forms have very sofisticated email validation and there is not much bnenefit from using addEmail() which produces html input type email with HTML5 validation, which anyway the javascript of Nette Forms switch off ;).

Last edited by m.brecher (2023-03-23 16:44)

m.brecher
Generous Backer | 736
+
0
-

@Nikolas

I've found /vendor/nette/forms/examples/localization.php which helped me a lot to understand a bit of the principle of localization in Nette. But there is no hint in the docs (or I've overseen it?) Proposal: mention that!

Yes, I will look on this and if possible prepare some PR into the official Nette documentation – until 31.4.2023

Last edited by m.brecher (2023-03-23 16:51)

Nicolas_K
Member | 25
+
0
-

@mbrecher

Thank you again, Miloš!

I will try with addText() .
(Actually still fighting with the translator, but there are little successes…)
BTW: there is no difference between b) and c) – is it?
BTW 2: mostly I'm on the ride with my VPN – sometimes sunny Croatia, sometimes rainy Ireland, seldom Switzerland… ;)

Many thanks and Ahoi!

Marek Bartoš
Nette Blogger | 1165
+
+2
-

I solved translations in forms by not using translator in forms :D

You can simply translate string before passing it to form.
Only thing you have to deal with is translation of default messages. That can be done either on app startup or when first form is created (if you have a form factory)
And because I am lazy to write $this->translator->translate(), added a t() shortcut for it

Last edited by Marek Bartoš (2023-03-29 17:17)

m.brecher
Generous Backer | 736
+
0
-

@Nicolas_K

BTW: there is no difference between b) and c) – is it?

Yes, I dont remember what I want to write in c) but b) should be OK

My first translator looks like this, may be it can help you to create your own translator:

<?php

declare(strict_types=1);

namespace App\Model;

use Nette\Database\Explorer;
use Nette\Utils\Strings;

class Translator implements \Nette\Localization\Translator
{
    public const DefaultLanguage = 'cs';

    private const MaxLength = 100;

    private string $lang;

    public function __construct( private Explorer $database )
    {}

    public function setLang(string $lang)
    {
        $this->lang = $lang;
    }

    public function translate(mixed $source, mixed ...$parameters): string
    {
        $source = Strings::truncate($source, self::MaxLength);
        if(!$source || $this->lang === self::DefaultLanguage){
            return $source;
        }
        return $this->tryGetTranslation($source);
    }

    private function tryGetTranslation(string $source): string
    {
        [$registered, $result] = $this->getTranslation($source);
        if($result){
            return $result;
        }
        if(!$registered){
            $this->registerTranslation($source);
        }
        return preg_replace('~[^ ]~', '*', $source);
    }

    private function getTranslation(string $source): array
    {
        $defaultColumn = 'text_'.self::DefaultLanguage;
        $translatedColumn = 'text_'.$this->lang;
        $translation = $this->database->table('template_text')->where($defaultColumn.' ?', $source)->fetch();
        $registered = (bool)$translation;
        $result = $translation?->{$translatedColumn};
        return [$registered, $result];
    }

    private function registerTranslation(string $source): void
    {
        $defaultColumn = 'text_'.self::DefaultLanguage;
        $this->database->table('template_text')->insert([$defaultColumn => $source]);
    }
}

The translations are stored in database table:

-- Adminer 4.8.1 MySQL 8.0.17 dump

SET NAMES utf8;
SET time_zone = '+00:00';
SET foreign_key_checks = 0;
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';

SET NAMES utf8mb4;

DROP TABLE IF EXISTS `template_text`;
CREATE TABLE `template_text` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `text_cs` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_czech_ci DEFAULT '',
  `text_de` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_czech_ci DEFAULT '',
  `date_created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `date_edited` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `text_cs` (`text_cs`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_czech_ci;


-- 2023-03-29 02:16:58

It works very simply: if the language is default, no sql query is made, the text from template is shipped, if another language the query into database is made, if no translation in database are found it returns ******* instead of the text to the html output. After adding new text into template/form the translator registers in database new text to be translated. Than you should go into the table and by hand fill the translations. After than it works. I have deployed this translator on real very simple webpage and works 100%.

Notice: The query into database are made via Nette\Database\Explorer ;)

Last edited by m.brecher (2023-03-29 04:24)

Nicolas_K
Member | 25
+
0
-

Thank you, Miloš and Marek!

I've got translation on the email-input to work (addText)
and I've got translations (all over) to work!

Actually I have 4 fields of translation,
handled by 1: directories and 2–4: a neon-file (one for all) :

  1. content = markdown files in different lang-subdirectories (presenter 1)
  2. contact-form = component for form with languages from neon-file (presenter 2)
  3. flash-messages = langs from neon-file (presenter 2)
  4. menu (hamburger.latte ;) including language as signal (and then via neon-file)

(The code is a haystack, since I've to recover from my “imperative-illness”, i.e. learning consequent coding in OO style..)

For now I'll be trying to run further in my own shoes
and close with “many thanks for your help!!” !

silviustan
Member | 22
+
+1
-

Hi @Nicolas_K ,

For textareas you can use placeholders too, and those are translated, right? Why to use setDefaultValue, which set the value, when you need a hint for it?

So, instead of

$form->addTextArea('form_hint', 'Mitteilung:', 40, 3)
	->setDisabled()
	->setDefaultValue("Hier steht was zur Erläuterung");

use

$form->addTextArea('form_hint', 'Mitteilung:', 40, 3)
	->setDisabled()
	->setHtmlAttribute('placeholder', 'Hier steht was zur Erläuterung');
Nicolas_K
Member | 25
+
0
-

Hi @silviustan ,

Thank you for your hint, I know that and I practice it so.
I misused the textarea to display some text (as DefaultValue),
which is more opaque than a Placeholder.
Actually – when intrinsic translation is not possible, I prepare extrinsic translation.
Which works well.

Thank you!