i18n: add option to mark string as non-translatable (#71791) #36

Merged
fpeters merged 1 commits from wip/71791-mark-as-untranslatable into main 2023-01-17 09:11:48 +01:00
5 changed files with 76 additions and 5 deletions

View File

@ -312,3 +312,50 @@ def test_i18n_pagination(pub):
resp = resp.click('20')
resp = resp.click('3')
assert 'offset=40' in resp.request.url
def test_i18n_mark_as_non_translatabe(pub):
TranslatableMessage.wipe()
Workflow.wipe()
FormDef.wipe()
BlockDef.wipe()
Category.wipe()
CardDef.wipe()
MailTemplate.wipe()
create_superuser(pub)
workflow = Workflow(name='workflow')
workflow.add_status('First Status')
workflow.add_status('Second Status')
workflow.store()
app = login(get_app(pub))
# first time goes to scanning
resp = app.get('/backoffice/i18n/', status=302)
resp = resp.follow()
resp = resp.click('Go to multilinguism page')
# second time, the page stays on
resp = app.get('/backoffice/i18n/', status=200)
assert TranslatableMessage.count() == 2 # First Status / Second Status
assert resp.pyquery('tr').length == 2
# check form filter
assert resp.form['lang'].value == 'fr'
resp.form['q'] = 'First'
resp = resp.form.submit()
assert resp.pyquery('tr').length == 1
# mark a message as non translatable
resp = resp.click('edit', index=0)
resp.form['non_translatable'].checked = True
resp = resp.form.submit('submit').follow()
msg = TranslatableMessage.select([Equal('string', 'First Status')])[0]
assert msg.translatable is False
resp = app.get('/backoffice/i18n/', status=200)
assert resp.pyquery('tr').length == 1
assert resp.pyquery('tr td:first-child').text() == 'Second Status'
resp.form['non_translatable'].checked = True
resp = resp.form.submit('submit')
assert resp.pyquery('tr').length == 1
assert resp.pyquery('tr td:first-child').text() == 'First Status'

View File

@ -32,7 +32,7 @@ from wcs.qommon import _, errors, get_cfg, misc, ods, template
from wcs.qommon.afterjobs import AfterJob
from wcs.qommon.backoffice.listing import pagination_links
from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import FileWidget, Form, RadiobuttonsWidget, TextWidget
from wcs.qommon.form import CheckboxWidget, FileWidget, Form, RadiobuttonsWidget, TextWidget
from wcs.qommon.storage import Equal, FtsMatch, ILike, Or
from wcs.workflows import Workflow
@ -69,6 +69,7 @@ class I18nDirectory(Directory):
get_response().breadcrumb.append(('i18n/', _('Multilinguism')))
criterias = []
criterias.append(Equal('translatable', not (bool(get_request().form.get('non_translatable')))))
if get_request().form.get('q'):
search_term = get_request().form.get('q')
criterias.append(Or([ILike('string', search_term), FtsMatch(search_term)]))
@ -85,6 +86,7 @@ class I18nDirectory(Directory):
'pagination_links': pagination_links(offset, limit, total_count, load_js=False),
'messages': TranslatableMessage.select(criterias, offset=offset, limit=limit, order_by='string'),
'query': get_request().get_query(),
'non_translatable': get_request().form.get('non-translatable'),
}
get_response().add_javascript(['popup.js'])
@ -211,6 +213,12 @@ class MessageDirectory(Directory):
rows = min(10, max(2, self.msg.string.count('\n')))
attr = 'string_%s' % self.lang
form.add(TextWidget, 'translation', value=getattr(self.msg, attr), rows=rows)
form.add(
CheckboxWidget,
'non_translatable',
title=_('Mark as non-translatable'),
value=not (self.msg.translatable),
)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
@ -218,6 +226,7 @@ class MessageDirectory(Directory):
if form.is_submitted() and not form.has_errors():
setattr(self.msg, attr, form.get_widget('translation').parse())
self.msg.translatable = not (form.get_widget('non_translatable').parse())
self.msg.store()
update_digests()
return redirect('../../?' + get_request().get_query())

View File

@ -22,6 +22,7 @@ class TranslatableMessage(sql.TranslatableMessage):
string = None
context = None
locations = None # list
translatable = True
def translations(self):
return {x.split('_', 1)[1]: getattr(self, x) for x in self.__dict__ if x.startswith('string_')}

View File

@ -4215,6 +4215,7 @@ class TranslatableMessage(SqlMixin):
('context', 'varchar'),
('locations', 'varchar[]'),
('last_update_time', 'timestamptz'),
('translatable', 'boolean'),
]
id = None
@ -4238,6 +4239,7 @@ class TranslatableMessage(SqlMixin):
context VARCHAR,
locations VARCHAR[],
last_update_time TIMESTAMPTZ,
translatable BOOLEAN DEFAULT TRUE,
fts TSVECTOR
)'''
% table_name
@ -4250,6 +4252,9 @@ class TranslatableMessage(SqlMixin):
)
existing_fields = {x[0] for x in cur.fetchall()}
if 'translatable' not in existing_fields:
cur.execute('''ALTER TABLE %s ADD COLUMN translatable BOOLEAN DEFAULT(TRUE)''' % table_name)
# add columns for translations
for field in cls.get_data_fields():
if field not in existing_fields:
@ -4306,7 +4311,10 @@ class TranslatableMessage(SqlMixin):
@guard_postgres
def load_as_catalog(cls, language):
conn, cur = get_connection_and_cursor()
sql_statement = 'SELECT context, string, string_%s FROM %s' % (language, cls._table_name)
sql_statement = 'SELECT context, string, string_%s FROM %s WHERE translatable = TRUE' % (
language,
cls._table_name,
)
cur.execute(sql_statement)
catalog = {(x[0], x[1]): x[2] for x in cur.fetchall()}
conn.commit()
@ -5060,7 +5068,7 @@ def get_period_total(
# latest migration, number + description (description is not used
# programmaticaly but will make sure git conflicts if two migrations are
# separately added with the same number)
SQL_LEVEL = (78, 'add audit table')
SQL_LEVEL = (79, 'add translatable column to TranslatableMessage table')
def migrate_global_views(conn, cur):
@ -5239,8 +5247,9 @@ def migrate():
# 67: re-migrate legacy tokens
do_tokens_table()
migrate_legacy_tokens()
if sql_level < 68:
if sql_level < 79:
# 68: multilinguism
# 79: add translatable column to TranslatableMessage table
TranslatableMessage.do_table()
if sql_level < 72:
# 72: add testdef table

View File

@ -14,7 +14,12 @@
{{ block.super }}
<form action="." class="i18n-filter-form">
<label>{% trans "Search:" %} <input type="search" name="q" value="{{q|default:""}}"></label>
<span>
<label>{% trans "Search:" %} <input type="search" name="q" value="{{q|default:""}}"></label>
<label><input name="non_translatable" type="checkbox"
{% if non_translatable %}checked{% endif %}
>{% trans "Search non-translatable strings" %}</label>
</span>
<fieldset class="radiogroup"><legend>{% trans "Language:" %}</legend>
{% for language in supported_languages %}
<input type="radio" name="lang" value="{{language.0}}" id="radio-lang-{{language.0}}"