Programming error in news controller when using a new language (Italian)

Dear all,
we managed to translate all indico to Italian and now we’re at 100% on transifex. I followed the instruction on how to add a new language and actually I got a wheel and a running instance where the Italian language it’s available.
The problem I’m facing it’s that in some cases (e.g. adding/deleting a site news item) I got this error:

2023-10-03 17:17:38,845  fbff868f2e134bb7  1       indico.flask - ERROR errors.py:110 -- 'titolo'

Traceback (most recent call last):
  File "/opt/indico/.venv/lib/python3.9/site-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File "/opt/indico/.venv/lib/python3.9/site-packages/flask/app.py", line 1469, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/web/flask/util.py", line 79, in wrapper
    return obj().process()
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/web/rh.py", line 285, in process
    res = self._do_process()
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/web/rh.py", line 256, in _do_process
    rv = self._process()
  File "/opt/indico/.venv/lib/python3.9/site-packages/indico/modules/news/controllers.py", line 85, in _process
    flash(_("News '{title}' has been posted").format(title=item.title), 'success')
KeyError: 'titolo'

where “titolo” it’s the Italian translation for “title”. I changed line 85 of controller.py and putting literally “placeholder” instead of “title” the operations goes fine. It seems that the string title it’s translated before the args are applied to the string, so generating the error. As for other languages this does not happen (e.g. french and Spanish) I would like to understand what I’m missing.
Could anyone suggest how to proceed ? Obviously, the following line it’s the same and it works, but I wouldn’t do changes to the core…

flash(_("News '{}' has been posted").format(item.title), 'success')

That’s a bug in the translation

Placeholders must obviously not be translated.

Tip: If you have the translations locally, run indico i18n check-format-strings which should find all/most of these issues:

[adrian@eluvian:~/dev/indico/py3/src:master %$]> indico i18n check-format-strings
Found invalid format strings in it_IT/LC_MESSAGES/messages.po
You have moved {count} event to the category "{cat}" | Hai spostato un evento nella categoria "{cat}"
['{count}', '{cat}'] != ['{cat}']
You have requested the move of {count} event to the category "{cat}" | Hai proposto di spostare un evento nella categoria "{cat}"
['{count}', '{cat}'] != ['{cat}']
The URL template must contain the '{value}' placeholder. | Il modello di URL deve contenere il '{valore}'. segnaposto
['{value}'] != ['{valore}']
The URL template must contain the placeholder '{value}'. | Il modello di URL deve contenere il segnaposto "{valore}".
['{value}'] != ['{valore}']
{count} abstracts have been deleted. | {contare} abstract sono stati eliminati.
['{count}'] != ['{contare}']
Please log in as {name} to sign this agreement. | Per cortesia effettua l'accesso come {nome} per firmare questo accordo.
['{name}'] != ['{nome}']
Row {row}: invalid email address: {email} | Riga {}: indirizzo email non valido: {email}
['{row}', '{email}'] != ['{email}']
{count} contributions have been deleted. | {conta} contributi sono stati cancellati.
['{count}'] != ['{conta}']
Feature disabled: {features} (not available for this event type) | Caratteristica disabilitata {caratteristiche} (non disponibile per questo tipo di evento)
['{features}'] != ['{caratteristiche}']
Features disabled: {features} (not available for this event type) | Caratteristiche disabilitate {caratteristiche} (non disponibile per questo tipo di evento)
['{features}'] != ['{caratteristiche}']
Feature enabled: {features} | Caratteristica abilitata : {caratteristiche}
['{features}'] != ['{caratteristiche}']
Features enabled: {features} | Caratteristiche abilitate : {caratteristiche}
['{features}'] != ['{caratteristiche}']
Feature disabled: {features} | Caratteristica disabilitata : {caratteristiche}
['{features}'] != ['{caratteristiche}']
Features disabled: {features} | Caratteristiche disabilitate : {caratteristiche}
['{features}'] != ['{caratteristiche}']
If you customize the title, that title is used regardless of the user's language preference.  The default title <strong>{title}</strong> is displayed in the user's language. | Se personalizzate il titolo, questo viene utilizzato indipendentemente dalle preferenze linguistiche dell'utente. Il titolo predefinito <stro
ng>{titolo}</strong> viene visualizzato nella lingua dell'utente.
['{title}'] != ['{titolo}']
Custom CSS file ({name}) | File CSS personalizzato ({nome})
['{name}'] != ['{nome}']
The image '{name}' has an invalid type ({type}); only JPG, GIF and PNG are allowed. | L'immagine '{nome}' ha un tipo non valido ({tipo}); sono consentiti solo JPG, GIF e PNG.
['{type}', '{name}'] != ['{tipo}', '{nome}']
Edit session "{name}" | Modifica la sessione "{nome}"
['{name}'] != ['{nome}']
Edit contribution "{name}" | Modifica il contributo "{nome}"
['{name}'] != ['{nome}']
Edit folder "{name}" | Modifica la cartella "{nome}".
['{name}'] != ['{nome}']
Edit attachment "{name}" | Modifica allegato "{nome}"
['{name}'] != ['{nome}']
Event "{event}" has been moved to category "{category}" | L'evento "{evento}" è stato spostato nella categoria "{categoria}".
['{event}', '{category}'] != ['{categoria}', '{evento}']
Moving the event "{event}" to "{category}" has been requested and is pending approval | Lo spostamento dell'evento "{evento}" in "{categoria}" è stato richiesto ed è in attesa di approvazione.
['{event}', '{category}'] != ['{categoria}', '{evento}']
At most {max_choices} option can be selected | Al massimo {max_scelte} opzione può essere selezionato
['{max_choices}'] != ['{max_scelte}']
At most {max_choices} options can be selected | Al massimo {max_scelte} opzioni possono essere selezionati
['{max_choices}'] != ['{max_scelte}']
Section "{title}" added | Sezione "{titolo}" aggiunto
['{title}'] != ['{titolo}']
Section "{title}" updated | Sezione "{titolo}" aggiornato
['{title}'] != ['{titolo}']
Section "{title}" deleted | Sezione "{titolo}" eliminato
['{title}'] != ['{titolo}']
Question "{title}" added | Domanda "{titolo}" aggiunta
['{title}'] != ['{titolo}']
Question "{title}" updated | Domanda "{titolo}" aggiornata
['{title}'] != ['{titolo}']
Question "{title}" deleted | Domanda "{titolo}" cancellata
['{title}'] != ['{titolo}']
The group '{name}' has been created. | Il gruppo '{nome}'è stato creato.
['{name}'] != ['{nome}']
The group '{name}' has been updated. | Il gruppo '{nome}'è stato aggiornato.
['{name}'] != ['{nome}']
The group '{name}' has been deleted. | Il gruppo '{nome}'è stato cancellato.
['{name}'] != ['{nome}']
News '{title}' has been posted | La notizia '{titolo}' è stata pubblicata
['{title}'] != ['{titolo}']
News '{title}' has been updated | La notizia '{titolo}' è stata aggiornata
['{title}'] != ['{titolo}']
News '{title}' has been deleted | La notizia '{titolo}' è stata cancellata
['{title}'] != ['{titolo}']
The file has an invalid format ({format}). | Il file ha un formato non valido ({formato}).
Admin added: {name} ({email}) | Amministratore aggiunto : {nome} ({email})
['{name}', '{email}'] != ['{email}', '{nome}']
Admin removed: {name} ({email}) | Amministratore rimosso: {nome} ({email})
['{name}', '{email}'] != ['{email}', '{nome}']
{name} has been blocked. | {nome} è stato bloccato.
['{name}'] != ['{nome}']
{name} has been unblocked. | {nome} è stato sbloccato.
['{name}'] != ['{nome}']
Your {field_name} has been synchronized from '{old_value}' to '{new_value}'. | Il vostro {nome_campo} è stato sincronizzato da '{vecchio_valore}' a '{nuovo_valore}'.
['{new_value}', '{old_value}', '{field_name}'] != ['{nuovo_valore}', '{nome_campo}', '{vecchio_valore}']
Your email address could not be synchronized from '{old_value}' to '{new_value}' since the new email address is not valid. | Non è stato possibile sincronizzare l'indirizzo email da '{vecchio_valore}' a '{nuovo_valore}' poiché il nuovo indirizzo email risulta non valido.
['{new_value}', '{old_value}'] != ['{nuovo_valore}', '{vecchio_valore}']
Your email address could not be synchronized from '{old_value}' to '{new_value}' due to a conflict with another Indico profile. | Non è stato possibile sincronizzare l'indirizzo email da '{vecchio_valore}' a '{nuovo_valore}' a causa di un conflitto con un altro profilo Indico.
['{new_value}', '{old_value}'] != ['{nuovo_valore}', '{vecchio_valore}']
This {plugin_name} room is already attached to the event. | Questa {nome_plugin}  stanza è già collegata all'evento.
['{plugin_name}'] != ['{nome_plugin}']
You are not allowed to create {plugin_name} rooms for this event. | Non sei consentito a creare  {plugin_nome} stanze  per questo evento.
['{plugin_name}'] != ['{plugin_nome}']
{plugin_name} room '{room.name}' created | {plugin_nome} stanza '{stanza.nome}' creata
['{room.name}', '{plugin_name}'] != ['{stanza.nome}', '{plugin_nome}']
{plugin_name} room '{room.name}' updated | {plugin_nome} stanza '{stanza.nome}' aggiornata
['{room.name}', '{plugin_name}'] != ['{stanza.nome}', '{plugin_nome}']
{plugin_name} room '{room.name}' refreshed | {nome_plugin} stanza '{nome.stanza}' aggiornata
['{room.name}', '{plugin_name}'] != ['{nome_plugin}', '{nome.stanza}']
{plugin_name} room '{room.name}' removed | {nome_plugin} stanza '{nome.stanza}' rimossa
['{room.name}', '{plugin_name}'] != ['{nome_plugin}', '{nome.stanza}']
You are not allowed to attach {plugin_name} rooms to this event. | Non è consentito allegare stanze {nome_plugin} a questo evento.
['{plugin_name}'] != ['{nome_plugin}']
{date_fmt} 'at' {time_fmt} | {data_fmt} 'alle' {ora_fmt}
['{date_fmt}', '{time_fmt}'] != ['{data_fmt}', '{ora_fmt}']
Login failed: {error} | Login fallito : {errore}
['{error}'] != ['{errore}']
Must be between {earliest} and {latest}. | Deve essere compreso tra {primo} e {ultimo}.
['{latest}', '{earliest}'] != ['{primo}', '{ultimo}']
Must be later than {earliest}. | Deve essere successivo a {precedente}.
['{earliest}'] != ['{precedente}']
Must be earlier than {latest}. | Deve essere precedente a {ultimo}.
['{latest}'] != ['{ultimo}']

Found invalid format strings in it_IT/LC_MESSAGES/messages-react.po
Disconnecting the editing workflow service while it is unavailable may leave your editing process in an inconsistent state. Do not perform this action if you plan to continue using the workflow service later. {strong}The current error with the workflow service is most likely temporary. Attempting to disconnect and reconnect will most likely not fix it.{/strong} | Rimuovendo il collegamento al servizio di workflow di modifica potresti lasciare il processo di modifica in uno stato incoerente mentre non è disponibile. Non eseguire questa azione se intendi continuare a usare il servizio di workflow in un secondo momento. {L'errore attuale del servizio di workflow è molto probabilmente temporaneo. Tentare di disconnettersi e riconnettersi non risolverà il problema.
['{strong}', '{/strong}'] != []
If you are a developer looking to implement a custom workflow, head over to the {link}reference implementation of the OpenReferee spec on GitHub{/link}. | Se sei uno sviluppatore e vuoi implementare un workflow personalizzato, consulta la documentazione di riferimento su GitHub{/link}.
['{link}', '{/link}'] != ['{/link}']
Glob-style filename template that all files of this type have to conform to (e.g. {example}). No dots allowed. It is possible to use {placeholder} as a placeholder for the contribution program code. | Modello di nome del file in stile globale a cui tutti i file di questo tipo devono conformarsi (ad esempio {esempio}). Non sono ammessi punti. È possibile utilizzare {placeholder} come segnaposto per il codice del contributo del programma.
['{placeholder}', '{example}'] != ['{placeholder}', '{esempio}']
At most {n} option can be selected | è possibile sezionare una sola opzione
['{n}'] != []
The occurrence you chose ({date}) cannot be cancelled. | L'occorrenza scelta ({data}) non può essere annullata.
['{date}'] != ['{data}']

There are A LOT of them. Too many for me to fix myself (which I tend to do if there’s just a handful). Please fix them (or have your translator fix them if it wasn’t you doing those translations).

Just an FYI, we’ve updated the Transifex settings so that it’ll highlight placeholders using {...} same as it currently does for %(...)s so there’s less of a chance for confusion :slight_smile:

Thanks for your prompt suggestion: with the command suggested I got all the errors that we’ll fix asap.
Probably a misunderstanding with a colleague who’s not a programmer.
Anyway, can I suggest to add the check command to the documentation here : Translations — Indico 3.3-dev documentation ?

We’ve updated the docs to include the command :slight_smile:

Now the Italian language pass the check-format-string without errors anymore.
Thanks a lot again for the kind suggestion, the automated check was essential to catch the errors !