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('20')
resp = resp.click('3') resp = resp.click('3')
assert 'offset=40' in resp.request.url 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.afterjobs import AfterJob
from wcs.qommon.backoffice.listing import pagination_links from wcs.qommon.backoffice.listing import pagination_links
from wcs.qommon.backoffice.menu import html_top 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.qommon.storage import Equal, FtsMatch, ILike, Or
from wcs.workflows import Workflow from wcs.workflows import Workflow
@ -69,6 +69,7 @@ class I18nDirectory(Directory):
get_response().breadcrumb.append(('i18n/', _('Multilinguism'))) get_response().breadcrumb.append(('i18n/', _('Multilinguism')))
criterias = [] criterias = []
criterias.append(Equal('translatable', not (bool(get_request().form.get('non_translatable')))))
if get_request().form.get('q'): if get_request().form.get('q'):
search_term = get_request().form.get('q') search_term = get_request().form.get('q')
criterias.append(Or([ILike('string', search_term), FtsMatch(search_term)])) 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), 'pagination_links': pagination_links(offset, limit, total_count, load_js=False),
'messages': TranslatableMessage.select(criterias, offset=offset, limit=limit, order_by='string'), 'messages': TranslatableMessage.select(criterias, offset=offset, limit=limit, order_by='string'),
'query': get_request().get_query(), 'query': get_request().get_query(),
'non_translatable': get_request().form.get('non-translatable'),
} }
get_response().add_javascript(['popup.js']) get_response().add_javascript(['popup.js'])
@ -211,6 +213,12 @@ class MessageDirectory(Directory):
rows = min(10, max(2, self.msg.string.count('\n'))) rows = min(10, max(2, self.msg.string.count('\n')))
attr = 'string_%s' % self.lang attr = 'string_%s' % self.lang
form.add(TextWidget, 'translation', value=getattr(self.msg, attr), rows=rows) 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('submit', _('Submit'))
form.add_submit('cancel', _('Cancel')) form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse(): if form.get_widget('cancel').parse():
@ -218,6 +226,7 @@ class MessageDirectory(Directory):
if form.is_submitted() and not form.has_errors(): if form.is_submitted() and not form.has_errors():
setattr(self.msg, attr, form.get_widget('translation').parse()) setattr(self.msg, attr, form.get_widget('translation').parse())
self.msg.translatable = not (form.get_widget('non_translatable').parse())
self.msg.store() self.msg.store()
update_digests() update_digests()
return redirect('../../?' + get_request().get_query()) return redirect('../../?' + get_request().get_query())

View File

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

View File

@ -14,7 +14,12 @@
{{ block.super }} {{ block.super }}
<form action="." class="i18n-filter-form"> <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> <fieldset class="radiogroup"><legend>{% trans "Language:" %}</legend>
{% for language in supported_languages %} {% for language in supported_languages %}
<input type="radio" name="lang" value="{{language.0}}" id="radio-lang-{{language.0}}" <input type="radio" name="lang" value="{{language.0}}" id="radio-lang-{{language.0}}"