Goal

As part of the localization update integration in core, we are looking to add source tracking functionality in interface translations. The l10n_update module tracks localization source for translations, so sites can customize community provided strings and retain those customizations.

Implementation plan

- Add a new column in the locales_target table. This is called l10n_status in l10n_update. Not sure we want to call it status, but it sounds like it would work for now.
- Add constants for the status values. LOCALE_COMMUNITY_PROVIDED, LOCALE_USER_PROVIDED or something along these lines.
- Fill these values to LOCALE_COMMUNITY_PROVIDED by default (when .po files are imported)
- When manually editing strings on the user interface set to LOCALE_USER_PROVIDED
- When importing a .po file, extend options to possibly overwrite user provided strings (see l10n_update for options)
- When importing a .po file, also extend options to specify if the .po file was user provided or community provided (maybe this is covered by previous option?)

CommentFileSizeAuthor
#123 custom-string-changelog.patch728 bytesGábor Hojtsy
#119 1445004-119.patch45.37 KBSutharsan
#115 1445004-115-import.png125.63 KBroderik
#115 1445004-115.patch46.96 KBroderik
#115 1445004-108-115-interdiff.txt3.24 KBroderik
#114 1445004-114-import-fresh-site.png99.24 KBroderik
#114 1445004-114.patch46.56 KBroderik
#114 1445004-108-114-interdiff.txt2.89 KBroderik
#108 1445004-opt-107.patch45.36 KBandypost
#108 1445004-opt-107.interdiff.txt4.79 KBandypost
#107 1445004-105.patch44.42 KBandypost
#107 1445004-105-interdiff.txt1.42 KBandypost
#105 1445004-105.patch44.42 KBandypost
#105 1445004-105.interdiff.txt1.76 KBandypost
#105 1445004-opt-105.patch45.36 KBandypost
#105 1445004-opt-105.interdiff.txt5.12 KBandypost
#104 1445004-104.patch44.13 KBGábor Hojtsy
#102 1445004-102.patch44.32 KBGábor Hojtsy
#99 1445004-98.patch41.52 KBSutharsan
#98 1445004-97.patch41.52 KBSutharsan
#96 1445004-96.patch541 bytesSutharsan
#92 1445004-91.patch41.51 KBSutharsan
#83 1445004-77-import.png19.37 KBSutharsan
#83 1445004-77-export.png24.71 KBSutharsan
#77 1445004-77.patch33.86 KBSutharsan
#68 1445004-68.patch33.87 KBSutharsan
#63 1445004-customized-export-fieldset.jpg65.2 KBSutharsan
#62 1445004-customized-export-2.jpg65.88 KBSutharsan
#62 1445004-customized-export-fieldset.pdf64.41 KBSutharsan
#61 1445004-60.patch34.34 KBSutharsan
#60 1445004-60.patch0 bytesSutharsan
#58 1445004-58.patch33.23 KBSutharsan
#53 1445004-ui-translation-export-language.png53.3 KBsaltednut
#52 1445004-ui-translation-export.jpg50.56 KBsaltednut
#52 1445004-ui-translation-import.jpg95.6 KBsaltednut
#50 1445004-46.patch33.62 KBsaltednut
#47 1-getPoFile.txt531 bytesSutharsan
#47 2-badPoFile.txt401 bytesSutharsan
#47 3-getOverwritePoFile.txt332 bytesSutharsan
#45 1445004-45.patch33.55 KBSutharsan
#45 interdiff-41-45.txt1 KBSutharsan
#42 1445004-41.patch33.43 KBSutharsan
#41 1445004-41.patch33.43 KBGábor Hojtsy
#39 1445004-39.patch33.41 KBGábor Hojtsy
#37 1445004-36.patch35.19 KBGábor Hojtsy
#34 1445004-34.patch34.55 KBroderik
#31 1445004-31.patch34.56 KBGábor Hojtsy
#30 1445004-29.patch34.63 KBGábor Hojtsy
#28 1445004-28.patch27.87 KBGábor Hojtsy
#28 CompleteExport.png53.47 KBGábor Hojtsy
#28 Customize.png41.25 KBGábor Hojtsy
#24 ExportRework.png99.15 KBGábor Hojtsy
#24 1445004-24.patch26.51 KBGábor Hojtsy
#23 1445004-23.patch23.46 KBGábor Hojtsy
#23 SimplerImportUI.png216.46 KBGábor Hojtsy
#20 ExtraImportMode.png70.69 KBroderik
#20 1445004-15-20.interdiff.txt3.91 KBroderik
#20 1445004-20.patch23.2 KBroderik
#15 1445004-13.patch20.36 KBGábor Hojtsy
#12 1445004-12.patch20.19 KBGábor Hojtsy
#12 TranslatedStringSearch.png21.68 KBGábor Hojtsy
#12 UntranslatedStringSearch.png18.88 KBGábor Hojtsy
#12 ImportCustomized.png57.44 KBGábor Hojtsy
#12 ExportTranslations.png45.04 KBGábor Hojtsy
#12 1445004-12.patch20.19 KBGábor Hojtsy
#9 1445004-9.patch9.07 KBroderik
#6 1445004-4.patch6.31 KBSutharsan
#3 1445004-3.patch6.93 KBroderik
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Gábor Hojtsy’s picture

Issue tags: +sprint

Mark for sprint.

roderik’s picture

Assigned: Unassigned » roderik

Diving in.

roderik’s picture

FileSize
6.93 KB

OK -- the first part (being 4 out of 6 points above) is done, plus a suggestion from Gabor: provide a filtering option for the statuses, in the translation screen.

I'll continue this week, but upload what I have now. There's @todo's on basic stuff like thinking about the name of the added column... I'll rethink those when I did the last part of this issue.

Filtering options I added into an existing dropdown. This means that not all theoretical filtering options are possible (like 'untranslated strings plus those entered on the site') but I think that's fine.

roderik’s picture

Status: Active » Needs review

Setting to review for naming of constants, naming of UI stuff, etc. But I'll rethink that myself, when done with the rest of the issue. So it's still CNW too.

Other incoming suggestion from Gábor: make the added 'translation status' a filtering option in the .po export.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

@roderik: thanks for working on this! Can you continue bringing this forward?

Here are comments based on a code review.

+++ b/core/includes/gettext.incundefined
@@ -503,6 +503,7 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
+            'status' => LOCALE_TARGET_COMMUNITY_PROVIDED,

@@ -515,6 +516,7 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
+            'status' => LOCALE_TARGET_COMMUNITY_PROVIDED,

@@ -539,7 +541,8 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
-           'plural' => $plural
+           'plural' => $plural,
+           'status' => LOCALE_TARGET_COMMUNITY_PROVIDED,

I think these will need to be made dynamic once the "custom strings export" feature is in, which should let you import strings as custom even on the import UI.

+++ b/core/includes/locale.incundefined
@@ -73,6 +73,19 @@ define('LOCALE_JS_OBJECT_CONTEXT', '
 /**
+ * Status of translation string, signifying it's imported from an external
+ * source (e.g. .po file, translation server) and
+ * @todo finish description - truth in above probably depends on option value specified on import
+ */
+const LOCALE_TARGET_COMMUNITY_PROVIDED = 0;
+
+/**
+ * Status of translation string, signifying it's inserted by a user through the
+ * website UI
+ */
+const LOCALE_TARGET_USER_PROVIDED = 1;

First lines of phpdoc should be summary and on one line (max 80 chars). If you need more background info add it on a second line but separate with a linebreak from the first.

+++ b/core/modules/locale/locale.installundefined
@@ -188,6 +188,12 @@ function locale_schema() {
+      'status' => array(
+ // @todo naming of 'status'? Otherwise name 'source'? "target source", egh...
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0, // LOCALE_TARGET_COMMUNITY_PROVIDED
+      ),

I agree naming this 'status' in the schema, _TARGET_ in teh constant and source on the UI (and type in the dropdown :) is a bit too much variation ;)

What about sticking to source all around?

+++ b/core/modules/locale/locale.pages.incundefined
@@ -95,6 +102,7 @@ function _locale_translate_seek() {
+      array('data' => ($string['status'] == LOCALE_TARGET_USER_PROVIDED ? 'user': 'external'), 'align' => 'center'),

I don't know really great words for these values, but they should at least be t()-ed for display. I think t('Custom') and t('External') would be good.

Sutharsan’s picture

FileSize
6.31 KB

locale_update_8001() should migrate data from l10n_update. Check for the existence of 'l10n_status' column in the same table and copy the data into the new 'status' field.

Changed the naming of the translations to 'custom' translations. 'Entered on this site' may be confusing if a po-file is uploaded. I've considered 'default', 'original', 'community' and 'custom', 'overridden', 'local'. But chosen the text below to be the most straight forward and least jargon.

  • 'Translated strings'
  • 'Translated strings, default translation'
  • 'Translated strings, custom translation'

If we agree on this naming, we could change the field name 'status' to 'override'. (Not included in the patch)

The translations overview page does not need a 'status' column. The content of this column is 99(.9)% of the time 'default' According to the User Interface guide this should be avoided.

In locale_translate_edit_form_submit() only set the status to LOCALE_TARGET_USER_PROVIDED if the string actually changed. When a site uses multiple languages, the translation page presents multiple translatable fields. Only the translations which were changed should be flagged.

Gábor Hojtsy’s picture

Status: Needs work » Needs review

Marking needs review for testbot. Still needs to address my comments and new suggestions from Sutharsan.

Status: Needs review » Needs work

The last submitted patch, 1445004-4.patch, failed testing.

roderik’s picture

Status: Needs work » Needs review
FileSize
9.07 KB

Both, thanks for your comments. Sutharsan, thanks for the code changes with nearly all your comments.

I defer to Sutharsan's experience on naming, and personally like the term 'override' for the database field.

Constant names/strings have been changed to match the 'default/custom' in the proposed description. Note their names are now quite generic, because the 'property name' is not included in the constant names##, and DEFAULT/CUSTOM are pretty generic terms. I believe in this case, that's OK...

Further:

  • In the 'export' screen, I got into naming issues again (because even though I like the term "override", it can't be used as a noun that the user understands) - and used "status" again, and "kind of translation".
  • "For other kinds, strings will be exported without translation" -> is this the intended behavior? (i.e. all source strings will be in a .po file, even if you select you only want to export the custom translations? It is that way for the language selection...)
  • The 'setting correct status on import' (see first code block/comment in #5) is not done yet.

-----

## I'm probably not being clear, however I try to describe that. I mean the construct 'entity.propertyname=value' here maps to 'locale_target.override=value'. The constants LOCALE_TARGET_DEFAULT and LOCALE_TARGET_CUSTOM seem to be giving the 'entity' a value. They don't even name the 'property' which the value actually belongs to. But LOCALE_TARGET_OVERRIDE_DEFAULT and LOCALE_TARGET_OVERRIDE_CUSTOM don't seem clearer to me. Same if we'd put STATUS there.

Status: Needs review » Needs work

The last submitted patch, 1445004-9.patch, failed testing.

Gábor Hojtsy’s picture

Thanks for continuing the work on this! It is getting better but IMHO still needs some discussion. The real trick here is IMHO not the functionality itself but how to make it intuitive for users so we need to get that right :)

+++ b/core/includes/gettext.incundefined
@@ -866,13 +869,23 @@ function _locale_import_parse_quoted($string) {
-    $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language ORDER BY t.plid, t.plural", array(':language' => $language->langcode));
+    $where = 't.language = :language';
+    $args = array(':language' => $language->langcode);
+    if (isset($override)) {
+      $where .= ' AND override = :override';
+      $args[':override'] = $override;
+    }
+    $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND " . $where . " ORDER BY t.plid, t.plural", $args);

Queries like this are very good candidates for conversation to db_select() instead of string manipulation.

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -145,6 +145,17 @@ function locale_translate_export_po_form($form, &$form_state, $names) {
+  $form['override'] = array('#type' => 'select',
+    '#title' => t('Translation status'),
+    '#options' => array(
+      'translated' => t('Translated strings'),
+      'translated_default' => t('Translated strings, default translation'),
+      'translated_custom' => t('Translated strings, custom translation'),
+    ),
+    '#default_value' => 'translated',
+    '#description' => t('Select the kind of translations to export. For other kinds, strings will be exported without translation.'),

In this snippet only, we call this an override, then a translation status, then a kind of translation :/ Can we figure out a terminology that can be used all around? Or at least limit the variance to two?

Eg. "type", "source", "customized"... I think we need some more brainstorming here...

Let's say we use "customized". That works as a column name, it works as a label, and we can say "base translation" and "customized translation" referring to the "customized" property.

Customized is also used in the menu_links schema to essentially mean the same thing:

'customized' => array(
'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'small',
),

+++ b/core/modules/locale/locale.installundefined
@@ -188,6 +188,11 @@ function locale_schema() {
+      'status' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0, // LOCALE_TARGET_DEFAULT
+      ),

This should have been 'override', right?

+++ b/core/modules/locale/locale.installundefined
@@ -275,6 +280,25 @@ function locale_update_8001() {
+  // If l10n_update module is installed copy its status data.
+  if (db_field_exists('locales_target', 'l10n_status')) {
+    db_update('locales_target')
+      ->expression('override', 'l10n_status')
+      ->execute();

The table column should be dropped after its data being migrated I think.

Gábor Hojtsy’s picture

Updated patch based on my comments.

- renamed constants to LOCALE_CUSTOMIZED and LOCALE_NOT_CUSTOMIZED
- added more docs on the constants
- passed on customized flag in import process (yes, this shows the whole import code is a huge mess and needs refactoring, we are doing it in a small part at #532512: Plural string storage is broken, editing UI is missing and will do it a lot more later; adding in this new feature now is not an issue IMHO on that front, we have refactoring and new feature cycles constantly going to optimize the patch flow :)
- refactored the _locale_export_get_strings() code to use dbtng
- added checkbox UI on import form for customized flag
- reworked customized flag selector as a radio group on export form, cleaned up text
- fixed the schema
- removed l10n_status in upgrade path, since we have its data migrated
- made 'customized' its own select box / option and made it a dependent select box which only shows if the translation select says only translations

Did test the search UI changes. Did not test import/export.

Search UIs:
UntranslatedStringSearch.png
TranslatedStringSearch.png

Import UI change:
ImportCustomized.png

Export UI change:
ExportTranslations.png

Gábor Hojtsy’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 1445004-12.patch, failed testing.

Gábor Hojtsy’s picture

Issue tags: +Needs tests
FileSize
20.36 KB

Helps if the query is actually executed on export. Will eventually need test when we get to an otherwise passing patch with a finalized UI :)

Gábor Hojtsy’s picture

Status: Needs work » Needs review
Gábor Hojtsy’s picture

Title: Implement custom/community string bit on translations » Implement customized translation bit on translations
Sutharsan’s picture

Excellent work Gábor You couldn't wait untill we did this ;)

Gábor Hojtsy’s picture

Assigned: roderik » Unassigned
Status: Needs review » Needs work

@Sutharsan: well, its not done yet at all. Can you or roderik help with adding tests (import stuff as customized, edit other stuff, export stuff only customized, only non-customized, etc)? He pointed out to me in email that he likely does not have time anymore this week, so moving to unassigned.

Thank you all for working on this!

roderik’s picture

Status: Needs work » Needs review
FileSize
23.2 KB
3.91 KB
70.69 KB

@Gábor: you're right in unassigning me - but I could put in some time for a little review & extra change.

(Naming "customized": ah. consistency in is nice. UI works well. Constant names are growing on me, I'm starting to like them.
Dependent checkboxes: ooh, shiny! Wasn't used to those yet, hence me putting the stuff in one.)

--

There should be an extra import mode now - the idea copied from l10n_update, though not the exact implementation:

Basically, if you import something as 'customized' and then later you import newer 'default' translations, you should have the option to not overwrite your customized translations, only the 'default' ones.
I also made it so that it would work the other way around (i.e. importing a newer 'customized' file overwrites only 'customized' strings, never 'default' ones). Not sure if there is a realistic use case for that.

It makes for a horrible description in the import screen, though. I'd like to have another pair of eyes on that.

Also, as Gábor said, still Needs Work for tests.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

I agree it would be great to somehow shorted those sentences dramatically :) Let's see these?

Handling of existing translations
- Replace with new translations only if the same type (customized/uncustomized).
- Always replace with new translations.
- Keep all unmodified.

What do you think? (This does not use the same order, and I think this new option would be the best new default, no?)

Also on re-reading the text for the custom checkbox that I wrote, I think we can easily loose the "you import" wording, its is pretty overkill :) Smarter text++ :)

Lars Toomre’s picture

+++ b/core/includes/gettext.incundefined
@@ -26,8 +26,11 @@
  * @param $mode
  *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
  *   LOCALE_IMPORT_OVERWRITE.
+ * @param $customized
+ *   Whether the strings being imported should be saved as customized.
+ *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.

When this patch gets re-rolled, the docblocks like this one need some work.

The @param $customized needs the (optional) phrase to start the description as well as a statement of what the default value is. Also, I thought type hinting was recommended for D8 docblocks. Hence, something akin to the below would be more appropriate (wrapped at 80 characters):

 * @param int $customized
 *   (optional) A parameter indicating whether the imported stings should be saved with a customized flag.  The default is LOCALE_NOT_CUSTOMIZED.  Permissible values are LOCALE_CUSTOMIZED and LOCALE_NOT_CUSTOMIZED.   

Other parameters on some of the functions being modified in this patch also are missing the (optional) sting as well as type hinting. I would suggest that they get corrected in this patch as well.

Gábor Hojtsy’s picture

FileSize
216.46 KB
23.46 KB

Ok here is a more dramatic rework of the import UI in part based on just pure simplification with consultation with @yoroy. Sorry did not implement Lars' suggestions yet. Also did not align the actual import code to support the new UI. In case we want to make any further changes. I think we basically want to fill in a matrix of defaults for the protection of existing translations and then let users choose as they want. The defaults would IMHO be (in the name of protection by default):

Import type/Existing string If the existing translation is non-custom If the existing translation is custom
Non-custom imported Default to protect Default to protect
Custom imported Default to overwrite Default to protect

(Edit: fixed issues in the table :) The three checkboxes let users provide their choice along the lines of this matrix basically, but it looks much simpler than that hopefully :)

SimplerImportUI.png

Gábor Hojtsy’s picture

FileSize
26.51 KB
99.15 KB

There goes the export screen with similar rigor :) That was also pretty fascinating. Instead of using two fieldsets, it used #type item's as headers (OMG) and then shared the same submission function among two forms. It did use the #type actions wrapper for buttons, which is good practice to use, so applied that to the import screen to though (no visual change, just FAPI best practices). Again employed some states to hide irrelevant stuff when not useful.

Overall the goal is when we introduce yet another new feature here, we make it so as simple and straightforward as possible. Since the forms were complex enough already, this looks like we can't escape fixing them up. Sorry if this looks like a bit of an overkill but IMHO its a timely goal. Yes, the internal APIs are also bad, but we need to fix that elsewhere, because that is *way LOT* more code.

So if these UI tweaks look good, we need to re-fix the import/expor screens to work with these vs. the custom flags.

ExportRework.png

Gábor Hojtsy’s picture

Status: Needs work » Needs review

Most likely not going to pass, just handing this in so its easier to figure out where did I break all kinds of stuff in tests :D And to get review on the UI.

drifter’s picture

Status: Needs review » Needs work

I think people will have trouble figuring out what a "customized translation" vs. a "non-customized translation" is, and so they'll have trouble selecting the right checkboxes. How about keeping a set of radio buttons with better descriptions, something like:

New translations will be added. For strings that already have translations:

() Don't overwrite any existing ones
() Only overwrite built-in translations.
() Only overwrite custom translations.
() Overwrite all existing translations

I think this makes the "safe" option and the "these are the translations I want" options more clear.

kalman.hosszu’s picture

I agree with drifter about the import page modifications, and I think a description on the export page should helpful to explain what does "customized translation" stand for.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
41.25 KB
53.47 KB
27.87 KB

Here is a patch with an updated export screen. I've theoretically built in the backend support for the export to do as advertised on the UI, but did not really test (yet). Added a third option for completeness. So you can do things like 'export strings I did not translate yet to Afrikaans' or 'export things I customized from the community translations for Afrikaans'. If you select a language but do not check any of the checkboxes, the file would be totally empty, so in that case I choose to fall back on exporting a template. We can also throw back a form error for that case if that sounds better.

CompleteExport.png

@drifter, @kalman.hosszu: great feedback. I'm wondering if we can explain the customize process better by leading the user with that. Remember, we want to build in l10n_update, which would feed the site with community translations and the ones edited on the UI (or imported as custom) are customized. I think adding an explanation like "Custom strings are either ones you edited the translation of on this site or imported as custom" is especially good. We can bury this in the help text later if we want to / need to but not sure it should be in the form.

So I think we might want to lead with a UI change like this to let people learn what is customized? :) Is this is too radical? :)

Customize.png

Status: Needs review » Needs work

The last submitted patch, 1445004-28.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
34.63 KB

Tests fixed for import/export and import code made work with the current options.

@drifter: BTW "built-in translation" is not really what is going on right, the translations are in no way built-in, they are "stock", "provided by the community", etc. We have some more terminology discussion above that you can read up on.

Gábor Hojtsy’s picture

FileSize
34.56 KB

Also loose the unneeded LOCALE_IMPORT_* constants and fix one remaining place of invoking the import API with the old constant instead of array of options. (Should stop this for today :).

Status: Needs review » Needs work

The last submitted patch, 1445004-31.patch, failed testing.

Sutharsan’s picture

We don't have to agree yet on what is the best wording. Lets see if we can come up with two of three alternatives, make simple mockups (or write it in code) and try it out with a few people.

roderik’s picture

FileSize
34.55 KB

Oh. Wow. Sweet :D

Observations:

  • Whether or not (1 checkbox + 4) radio buttons vs. (3) checkboxes help guide the user better to the choices they need -- that might be, I don't know. Agree with making mockups for these, and leading those past some extra people.

  • But I think people will have a challenge to figure out what this customized vs. non-customized thing is, no matter what you call it. (Plus indeed, we have the issue that the term "built-in" isn't being clear on what it fully implies: also including downloaded community translations, in the future.) I don't see the radio buttons helping there.


    So yes, this needs good help text to ease the user into the options. Both on the explanation of the difference between custom vs. non-custom, and guiding the user with as little pain as possible, to what they really need

  • IMHO, "what they really need" is usually to overwrite non-custom translations - also in the case where the .po file is non-custom. That's much like l10n_update: "non-custom translations are whatever the community gives me, if they decide on updates, I want those too".


    The code (Gábor's matrix) sets "do not overwrite non-custom" by default, if the imported file is non-custom.


    So the reason for that is that this is the 'safest' option. Not that it's the 'most commonly used' option, because it isn't. Is that a fair statement to make?

  • So the help text would hopefully include something along the lines of "if you don't know/care what all this means and you are just importing a translation file from a source you trust... you want to click the 'Overwrite non-custom' [ checkbox / radio button ] ".

  • While I like the 'radical-ness' which fits the rest of what this issue is turning into :)... renaming of the tab wouldn't make things completely clear. 'customizing' can also be done on the "import" tab.

Patch contains no functional changes since patch 31, just a few minor things that caught my eye. I didn't test, only read code.

Gábor Hojtsy’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 1445004-34.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
35.19 KB

Fixed most tests by using the right nested form ID syntax (doh) and not attempting to delete non-existent translations.

Status: Needs review » Needs work

The last submitted patch, 1445004-36.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
33.41 KB

#532512: Plural string storage is broken, editing UI is missing landed, so many import/export things needed to be rerolled. Here is a new version. Will most likely still fail.

Status: Needs review » Needs work

The last submitted patch, 1445004-39.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
33.43 KB

Several update functions landed since, so needed to bump the number quite a bit.

Sutharsan’s picture

FileSize
33.43 KB

Bumping the testbot.

jthorson’s picture

Status: Needs work » Needs review

#41 and #42 are testbot-breakers ... for some reason, qa.d.o has troubles parsing the test result. Please do not requeue.

I hope to get back later today to debug what the root issue might be.

Status: Needs review » Needs work

The last submitted patch, 1445004-41.patch, failed testing.

Sutharsan’s picture

FileSize
1 KB
33.55 KB

The patch corrects a column definition. That may explain the broken test. But testing this patch fails locally on the po import test. But importing the same files manually passed. Lets see what the test bot thinks about it.

Status: Needs review » Needs work

The last submitted patch, 1445004-45.patch, failed testing.

Sutharsan’s picture

FileSize
332 bytes
401 bytes
531 bytes

These are exactly the tests that failed when I ran the tests locally (that's at least something). It looks like the plural formula is being overwritten. But when importing manually all is fine. Test files taken from the code are attached here.

Gábor Hojtsy’s picture

I know the test fails are more likely do to invasive changes we make in the import conditions. They mean somewhat different things after this patch. I took some time looking there earlier above extensively but did not reach conclusions unfortunately. I think we should re-evaluate the correctness of changes in conditions in the import process.

saltednut’s picture

Assigned: Unassigned » saltednut

Simpletest did not have the javascript behavior to automatically uncheck 'Include non-customized translations' so.. overwrite_options[not_customized] was always defaulting to 1. Patch forthcoming....

saltednut’s picture

FileSize
33.62 KB

Submitted for testing...

saltednut’s picture

Status: Needs work » Needs review
saltednut’s picture

Import
UI Translation Import

Export (Source Text)
UI Translation Export

saltednut’s picture

Export (Language Specific)
Export Translation (Language Specific)

pixelite’s picture

For the import and export UI, I agree that it's not clear what custom translations are. I think we should add some help text to the list of options: 'Custom translations are translations which haven't been downloaded from a centralized source like localize.drupal.org'.

The word 'Non-customized' could also be replaced with 'default'.

I also think that in the Import UI, the phrase 'Import contains custom translations' should be replaced with 'Import contains customized translations' to make the label consistent with the other options.

mbyrnes’s picture

These look really good! Definitely a simplified UI improvement with more fine grained options.

Here is a summary of feedback from the admin perspective looking at the current UI:

EXPORT

Source Text only option:

When I go to the export screen, I am looking for a language. Source text only doesn't really make sense as the sane default. Ideally it would be the main language on the site default that would be in here and that would be a non default option.
If this option was selected, it would make sense that it be explained what exactly it would deliver or what a use case might be. Why would I select this?

When Language is selected:

We should assume that most exports will include the entire file, so having the 3 checkboxes visible with the option to generate a blank file seems a bit confusing.
Perhaps an option to view these as more advanced options for people who select the option may be helpful.
"Customize content of file exposes the options" ie. Include customized translations, include non-customized translations, and include untranslated text.

IMPORT

When the language is selected, the three checkboxes don't seem related. The first check box "Import contains custom translations" describes the contents of the file. The 2nd two check boxes are actions that should be taken upon importing.
Because these are different groups, I suggest that we move the import contains custom translations to be nearer to the file field.
Terminiology:
Overwrite existing non customized translations
Overwrite existing customized translations

These options are only one word apart and it may be confusing for people. I don't have a current solution and I do like the 'customized' terminology. I just worry that they are visually similar and require a bit of mind bending to figure out exactly what this would get you.

One idea would be to possibly create summaries for each of the permutations. For example if you selected "import contains custom translation" AND "overwrite existing customized translations", a message would be given such as "You have selected file X to be imported, the updated translations in your file will replace what currently exists on the site."

pixelite’s picture

For the import UI, in order to make it clearer that the 'Import contains custom translations' checkbox is all-or-nothing, I think we should move it to a separate radio button field.

Label: This import consists of custom translations, Checkboxes: Yes/No

Berdir’s picture

Status: Needs review » Needs work

Coding style review.

+++ b/core/includes/gettext.inc
@@ -454,15 +467,34 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
+  if ($lid) {
+    $existing_customized = db_query("SELECT MAX(customized) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
+	}
+

Looks like a tab there on the last line with the }.

+++ b/core/includes/gettext.inc
@@ -479,25 +511,25 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
-      elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
-        // Translation exists, only overwrite if instructed.
+      elseif ($overwrite_options[$existing_customized ? 'customized' : 'not_customized']) { ¶
+				// Translation exists, only overwrite if instructed.
         db_update('locales_target')

More tabs and trailing spaces.

+++ b/core/modules/locale/locale.install
@@ -176,6 +176,12 @@ function locale_schema() {
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0, // LOCALE_NOT_CUSTOMIZED
+        'description' => 'Boolean indicating whether the translation is modified.',
+      ),

Comments should be on a separate line, even for cases like this one. Not sure how the other rules apply here (like having a proper sentence ending with a .)

+++ b/core/modules/locale/locale.install
@@ -485,6 +491,27 @@ function locale_update_8005() {
+    'not null' => TRUE,
+    'default' => 0, // LOCALE_NOT_CUSTOMIZED
+  );

Same here.

+++ b/core/modules/locale/locale.install
@@ -485,6 +491,27 @@ function locale_update_8005() {
+  // If l10n_update module was installed retain its status data, but drop the
+  // old field from the table.
+  if (db_field_exists('locales_target', 'l10n_status')) {
+    db_update('locales_target')
+      ->expression('customized', 'l10n_status')
+      ->execute();
+    db_drop_field('locales_target', 'l10n_status');
+  }

Core *usually* doesn't deal with contrib modules in the upgrade path. Even if stuff is moved into core (e.g. fields in D7).

Just wanted to point this out, I'm fine with keeping it if is ok'd by others.

+++ b/core/modules/locale/locale.pages.inc
@@ -36,13 +37,16 @@ function _locale_translate_seek() {
+      if ($query['customized'] != 'all') {
+        $sql_query->condition('t.customized', $query['customized'], '=');
+      }

The '=' is not necessary, that's the default.

Sutharsan’s picture

FileSize
33.23 KB
  • Spaces corrected.
  • Removal of 'l10n_status' column deleted from locale_update_8005(). When l10n_update lands in core, the is more clean-up work to do. It does not belong in this patch.
  • I do agree that we write comments on a new line. But what's the alternative here? A comment line within an array? Or comment like 'The below default value is the constant LOCALE_NOT_CUSTOMIZED'. I left it unchanged.
  • '=' removed

Still 'needs work' for #54 - #56

Sutharsan’s picture

Assigned: saltednut » Sutharsan
Sutharsan’s picture

FileSize
0 bytes

Export, Source Text only

When no language is available (system language only) a text is displayed instead of the (one option) select list.

Export, When Language is selected

Language default value changed to the site's default language.
Added a checkbox to hide/display the export options. Or should this be a collapsible fieldset?
Changed 'Include non-customized translations' to 'Include default translations'

Import

In order to explain a concept, it needs to have a name. Instead of 'default translation' and 'customized translation' I looked for a name that give these translations a natural state above the other translations. Base translation and Priority translation is my choise, but I am open to suggestions. I only changed the import and export interface, no name changes in the code yet.

Sutharsan’s picture

FileSize
34.34 KB

Try again ;)
Still needs work, I need to check the test. But would love feadback on the interface.

Sutharsan’s picture

Needs usability feedback on using a checkbox or collapsed fieldset to hide/show details of customized export.

Sutharsan’s picture

The fieldset variant:

Bojhan’s picture

I would prefer the option of using a collapsed fieldset. Although the "collapse upon clicking a checkbox" is a possible pattern, it is not one that performs very well with users (it is is magic to them, when a checkbox collapsed into more optionns).

Given that this is functionality, that looks like very experty (not for the 80%) - I would agree with the idea of putting them in a collapsed fieldset..

Bojhan’s picture

Issue tags: -Needs usability review
Gábor Hojtsy’s picture

Status: Needs work » Needs review

Fieldset looks good.

Status: Needs review » Needs work

The last submitted patch, 1445004-60.patch, failed testing.

Sutharsan’s picture

Status: Needs work » Needs review
FileSize
33.87 KB

Test now pass locally.

Schnitzel’s picture

Just heard about this Issue the first time.

I think it is a really good Idea, but I would suggest to call the "Translation Types" different:

- Custom Translation
- Community Translation

Sutharsan’s picture

Status: Needs review » Needs work

After a day, I realize that my 'priority' naming idea was not so well thought out. We are not used to calling things names in Drupal. Lets revert to 'default' and 'custom(ized)'.

But it made me realize that we are building power user functions into core here. First (second and third) time users of Drupal wil not import and export po's. All they want is "one button". Select a language and the're done. They may not like the translation, but bothering them with export a pot file, grab a translation tool, import a po file is way to far. And we are busy adding options to control yet another layer, the customized translation string. If we want to smoothen the learning curve the least we should do is use sensible defaults and hide them (fieldset). Better would be to move these interface options into a contrib module. But best we could do is move the complete import and export interface to a contrib module. Once we have Localization Update module in core, we should leave import and export to the power users which are best served by contrib.

Gábor Hojtsy’s picture

@Sutharsan: I would not be comfortable marketing Drupal 8 as a multilingual CMS if it does not let me change the interface translation, only if I go and edit obscure formatted .po files and then somehow re-import them on a one-button UI to reimport everything (which then might overwrite that file from a remote copy and I get lost all my changes). I think its both normal to assume that localize.drupal.org or any other localization server will not provide 100% translations and people need to work with plugging in the holes and that people will not 100% agree with the translation. Both are very current problems of people and the last one are even cause daily problems in translations teams.

I think we are making huge improvements to the import/export screens here already, so that is a usability improvement. And second, we need to build in custom string tracking because most people will need to use the translation UI to touch-up / extend on the built-in translation and we need this information for deployment/staging and later for a contrib module to let people contribute to the community their suggestions right away too (currently in l10n_client).

My thinking.

Sutharsan’s picture

I agree we need the plumbing, but do we need to add all possible options into the user interface. Yes, we need to add a custom translation to database and the API, yes l.d.o translations will never be 100%, and yes there need an interface to modify the default translation, yes these custom translations need to be treated different from the imported l.d.o translations. But I argue against bells and whistles for inexperienced users. For example the option to control whether imported translations will override the default and/or the custom translation, the options to export which flavor of translation (default, customized, untranslated). Using po files is not for the faint of hart, history proved this and lead us to build l.d.o. I do agree on the need for the underlying mechanisms which we are building in this issue. But to me core is not the place for full featured interfaces for power users.
[edit] some text changes

andypost’s picture

This issue require focus - there's to much context of translation forms!

There's a different angles to me here:
1) Developer (site builder) - need to translate config, some custom module's provides strings to UI of site's editors
2) Translator (site editor) - need to translate content, layout options, actions (links, buttons, rules) probably some custom UI elements (accordion, slider)

We could use the same element to translate strings and probably separate storage for different kind of strings

With customized bit we gets another dimension in strings' storage

not customized customized
lid 0 1
version 1 0 1
version 2 0 1

so it makes queries slower and we need different strategies for caching

The UI could be implemented in separate issue, so if we have no time for UI we still have storage, cache and api and contrib project to grow better UI

Gábor Hojtsy’s picture

@andypost: no this issue has a very clear focus. 1. It is only about strings coming from source code (not about translating config). 2. It does not store a customized and non-customized translation and does not use this bit at all in lookups (ie. will not store more copies of the data and will not slow down any lookup queries).

@Sutharsan: the .po import UI is already a power user tool if/once we add the l10n_update module. We are not moving ahead on that front much other than this patch (and this patch is locked in argue-land now anyway). So I think people who do end up using the automated l10n_update tools we want to build-in will not use this much anyway, and yes, it will be somewhat of a power tool. I still think if they want to stage/deploy their custom translations from a site to the live version, this import/export tool is the only thing we provide in core (outside of installing more contrib power tools), so depending on the use case, this looks like a basic tool for doing your deployment. Anyway, we are not moving forward with this one or any other of the l10n_update issues, so its hard to assume what would be ready by Drupal 8.

Sutharsan’s picture

@gábor: I'm glad to read that we have more common ground, than I initially thought. I have no intention blocking this issue. In fact I continued on this issue again to make another small variation of the interface. It's difficult to find the right terminology.
Should we update the module's help text. And should we anticipate in the text on (automatic) translations update?

balagan’s picture

For me this new UI looked quite straightforward, but it is better to be on the safe side with being as clear as possible.

Sutharsan’s picture

Status: Needs work » Needs review
FileSize
33.86 KB

Can't really improve this any more. I've tried different layout variations, but none did make it clearer non better layout. The patch changes only the 'priority' wording back to 'default' and 'custom(ized'). I left the help text unchanged as the translation handling is currently in flux and don't know where it will end.

Gábor Hojtsy’s picture

All right, I see @pixelite brought back the suggestion to use 'default' vs. 'customized' instead of 'non-customized' and 'customized' that we used for some time. I see @Surtharsan likely agrees. If people overall think this is better terminology, then go with that. I don't like it personally because (a) it is not in Drupal by default, it still needs to be downloaded (b) it can change overtime as translators work their way through fixing issues and extending community translations (c) the 'default' wording makes me feel like this is a given, I think it disconnects people from the fact that the production of those is also an ongoing process and is not 'done' for them per say (d) I can import new defaults that overwrite the old defaults.

I thought the best wording we found to signify this was the customized/non-customized distinction. If people believe 'default'/'customized' expresses the same thing, then let's go with that.

Otherwise just a minor note: "Treat imported strings as custom translations." has a dot at the end, while we don't do that on checkboxes, right? We don'd do that on other checkboxes in the same form.

eigentor’s picture

A very quick scan of the UI options from someone to whom even the concept of customized translation sets is completely new.

For me, maybe not wanting to use the feature it means more checkboxes. So in order not to bother me but to provide the people who want to use it with the extra power:

1. Make clever defaults so I do nothing wrong
2. An option that rather hides the extra options and is called "options" or "advanced options" or whatever like in http://drupal.org/node/1445004#comment-5779464 looks good, as it employs UI patterns that appear familiar to me.

Gábor Hojtsy’s picture

@eigentor: that is what is happening in the current patch yes :)

eigentor’s picture

Gabor asked for UI feedback.
Instant reactions to the new UI which I understand is rather on the right side of http://drupal.org/node/1445004#comment-5673836
Uh, oh, three checkboxes, which ones do I choose? Somehow fear to do something wrong.
Same with old interface, but if I am a fearsome person I choose "only import new, don't overwrite" and feel this will most probably not break anything.

Now I have three options
* import contains custom
* Overwrite existing customized
* Overwrite existing non-customized

Uh, oh, what?
If I was new to Drupal I would be completely confused and am even now...
I only want to import a translation and have no idea if it is custom or not, but I do not want to break any stuff I already have...

Would advocate to provide clever default settings or somehow name it in a way so I know how to import a regular .po file I got from localize.drupal.org or some official download place.

I guess the people that import customized translations know what they are doing. So these people would be a very small minority, and somehow the entire custom translation thing would be an "advanced option" for me.

Gábor Hojtsy’s picture

@eigentor: the current defaults are supposed to be the best guess for default behavior. They are not going to overwrite your existing data. Is the current Drupal 7 screen better? (see comparison in http://drupal.org/node/1445004#comment-5673836).

Sutharsan’s picture

FileSize
24.71 KB
19.37 KB

To aid the UI evaluation, some screenshots with the #77 patch applied:

Import

Export

For a default import and export settings we need a default scenario. I wrote a few, but note that I have little input from the real world of maintaining multilingual sites.

  1. Private translations: I maintain my own translation. I need it for it's tone of voice to match the audience of my websites . It is (re)used on different websites.
  2. Unmaintained language: I have a language which is not provided by l.d.o. The translation may or may not be shared with the community at some point in the future. I use this translation on all of my websites.
  3. Contributed translation: I have translated this module and contributed the translation to l.d.o But approval seems to be going slow over there and I want to use this translation on my website.
  4. Translation deployment: Translations on our websites are updated automatically using Localization Update module. But we modified some strings on the test site. We want to deploy these changes from test to acceptance and to live.

Which scenarios are most likely? On which should we base our default settings.

Gábor Hojtsy’s picture

@Sutharsan: I think the (4) translation deployment scenario is most likely. In that scenario, people would export their custom translations only and import their custom translations. **However** we don't have any of the automated imports to be built, so the current most likely scenario is "I've downloaded this .po file from drupal.org and need to import it", which is what the current defaults are aimed at. I think once/if we introduce automated .po file download + import, we can talk about revisiting this screen to have better defaults in that scenario. Building this prematurely for a state that we don't know if we will reach is not a good idea IMHO.

eigentor’s picture

@Gabor: right, the D7 screen is not that intuitive as well :)
I did not know that plural form bit until now, is this brand new?

The problem I stil see is: I think the user needs to choose one of those three options to import his .po file. The two old options still have me wondering every time if I am doing it right. With the three new ones, imagining I was a user new to drupal I have a very hard time choosing one as I understand them even less.

It is that all three sound a bit intimidating to me. But I guess "Overwrite default translations" would equal the old overwrite option and "Import Strings as custom translations" would be equal to "keep old, only import new"?

Now the challenge would be to name the three new ones in a way the casual user confidently chooses one.
Which one is chosen by default?

At last I think I am getting what this custom translation thing is all about: Someone has changed some translations in his current version of Drupal, and the new system makes sure he can safely import .po files without losing them, while still overwriting default translations?

Going really fancy there might be a mechanism that detects if any custom translations are there and only shows checkboxes / radios relating to that in that case. This would make the interface simpler for people who have not translated any string and just wanna import an innocent .po file.

But this might complicate matters on the programming side.

Gábor Hojtsy’s picture

It is that all three sound a bit intimidating to me. But I guess "Overwrite default translations" would equal the old overwrite option and "Import Strings as custom translations" would be equal to "keep old, only import new"?

No. There is one checkbox where you tell the system whether your file contains your own custom translations or it came from the community (yes/no). Then there are two checkboxes where you tell the system whether to overwrite the non-custom and the custom translations that exist respectively. If you click you are importing a custom translation file, it will automatically check "yes" for overwriting non-custom strings (called default on the current UI). If you import a non-custom file, the defaults will be no overwrites (no checkboxes checked). So the sensible defaults we made up is that either no checkbox is checked, or the first two are (if the first one is). Then you can tweak it from there. So the upload, the dropdown and the first checkbox tells us what the file is, and the two last checkboxes tell how it is going to be processed in case there are conflicts with strings already in the DB. In that sense, the last two checkboxes could be the advanced options for the import.

At last I think I am getting what this custom translation thing is all about: Someone has changed some translations in his current version of Drupal, and the new system makes sure he can safely import .po files without losing them, while still overwriting default translations?

Exactly!

Going really fancy there might be a mechanism that detects if any custom translations are there and only shows checkboxes / radios relating to that in that case. This would make the interface simpler for people who have not translated any string and just wanna import an innocent .po file.

That would require a four step process (1) you upload your file, ie. fill in the first three form elements (not the two last checkboxes) (2) we import your .po file to a temporary holding space somewhere and compare each strings to the data in the db, and devise if we need to ask the questions in the last two checkboxes (we very likely will need to) (3) we ask those questions (4) we import the file based on the answers to those. The current solution is a two step process, you provide all the data and then we do the import. I don't think moving to a four step process while we'd need to do the further questions almost all the time is a good idea.

@all: also note that the import UI now says "default translation" while the export UI says "base translation". Would be good to unify these. (I still think non-customized was fine :)

Sutharsan’s picture

@eigentor, This customized flag for translations is introduced by this patch to allow the admin to override translations and prevents them from being overwritten by automated translation updates like the Localization Update module. Translations edited via the translation UI will be marked as customized. Translations imported from l.d.o by Localization Update are not marked customized (e.g. become non-customized or default translation). In the same way, manually imported po files from l.d.o should become default translation. Customized translations are the admin's realm, default translations are the system's realm. Customized translations give the admin full control, default translations allow override by automatic updates.

Gábor Hojtsy’s picture

@eigentor: thought some more about this, the right defaults are really dependent on the situation :) If you are building out your site and importing translations from the community (localize.drupal.org), you want them to be "default" translations AND you want them to overwrite what is there, since you want to take the most up to date greatness. Then once you launch your site, you might or might not want to take translation updates like that. If you value a well greased site setup, you might be shocked to get things change their name on the UI because the translation team decided now "account" should be translated differently. So you are more likely want to take new translations (ie. for new strings appearing in later core/contrib versions), but keep the old ones. So its more of a question, of where your site is in its lifetime, and how much of a control freak you are :)

The default assumes you are more of a control freak, so it applies best to production sites (where most sites would spend more of their times vs. in development). However, when in development, your more sensible defaults are likely to allow for changes as long as you get the latest great stuff IMHO.

Gábor Hojtsy’s picture

BTW however for custom translations, I think its more likely you want to overwrite everything by default, since you want to get your changes through no matter what. We don't currently default to that. We default to custom strings overwriting default translations but not your existing custom translations. (Which might again be a safer default for production sites, if you want to distribute your customizations among various sites but keep per-site customizations intact). So again, we default to protecting existing data except the default strings which you likely want to override anyway.

Now the real question is how does all this help us design a better UI for this? I'm not sure it would make sense to have an API for this in without a UI to affect it (i.e an import needs to know exactly what it is allowed to overwrite and remove), so it would be great to be able to move in a direction that is relatively comfortable at least. As @eigentor said, the D7 UI is also puzzling...

eigentor’s picture

We default to custom strings overwriting default translations but not your existing custom translations.

O.K. this makes totally sense and should match 95% or more of users, and more importantly, the users that do not understand the system in depth.

So make this the default.

Now: should it be chosen by default and the options hidden behind an "options" fieldset or not.

- New users: would gain from that, because they do not need to worry and are not stopped by some fancy options that may make them hesitant and in consequence not import their .po file in fear of breaking something.

- Experienced Drupal users: would click the fieldset, find options, and as they are proficient problem-solvers (else they would never have sticked with Drupal that long) find an option that suits them, click that and are happy

- People that know the old UI and miss the two options to choose from: might go crazy, curse core developers, but in the end also click on the fieldset and find the options.

So that's what I would advocate for: make the option preselected that fits 80-95% of people importing a .po file and hide the confusing options behind a collapsible fieldset.

Gábor Hojtsy’s picture

@eigentor: I think we agree that sane defaults make sense, however said sane defaults are different between the development state of the site vs. the deployed version as explained above. So if we hide them as advanced options it will be "bogus" by default for development (with current defaults).

Sutharsan’s picture

FileSize
41.51 KB
  • Added tests for:
    • Import with overriding existing non-customized strings
    • Import with overriding existing customized strings
    • Export customized translations
    • Export untranslated strings
  • Hide field set with export options when no languages is selected.
  • Wording back to "non-customized"/"customized".

No changes to import default settings yet.

eigentor’s picture

Well don't wanna play the broom invoked by the sorcerers apprentice that you cannot stop anymore :)

If you really wanna nail the UI I guess the only real way is some quick and dirty user testing. And find out who are the main users of this screen. Yeah, sane default is important.
Who does the screen target primarily? This question needs to be answered. I find we still make the mistake in Drupal to target ourselves that already know Drupal too much, when it comes to UI decisions.

So I'm mostly taking the side of the novice.
But if the novice never goes there, this makes for a completely different situation.

Gábor Hojtsy’s picture

@eigentor: currently, this is the only UI/process we have for .po file imports in core. We want to build in l10n_update entirely, at which point this will be an advanced user UI only. So I think since we seem to be going in circles here now and my posts for translators at http://localize.drupal.org/node/4659 (syncdicated to planet) and on the translator mailing list did not result in complaints or any better ideas, we should proceed with this as we are and once we have the automated import in, then see if we need to have any adjustments (seems likely not). IMHO it would be better to focus on working on making this UI the advanced UI by building in the auto-import functionality, but we do need tracking of customized strings for the auto-import to be useful without killing your data, so we need to get over this to move forward.

What do people think about this?

Gábor Hojtsy’s picture

Status: Needs review » Needs work

Looks very good to me. Only found one issue:

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -118,65 +134,75 @@ function locale_translate_export_screen() {
+      '#states' => array(
+        'invisible' => array(
+           ':input[name="langcode"]' => array('value' => 'system'),
+        ),
+      ),

'system' should be LANGUAGE_SYSTEM.

Sutharsan’s picture

Status: Needs work » Needs review
FileSize
541 bytes

Patch modified with #95

Regarding the UI. I know this interface is difficult for Drupal newbies, I don't need a UX test for that. The best thing we can do for this user group is to prevent that they will need these pages in the first place. If they reach the import page, they have managed to downloaded a po file from l.d.o and want to import it into their site (and even reaching this point will be a challenge for them!). And that is exactly what Localization Update in core can help them with by its automatic imports from l.d.o. Even for experienced Drupal users this interface is challenging, but at lease we have a more limited set of scenarios to focus at. Lets get this issue rolling, it is a blocking issue for automated import.

Status: Needs review » Needs work

The last submitted patch, 1445004-96.patch, failed testing.

Sutharsan’s picture

Status: Needs work » Needs review
FileSize
41.52 KB

Ok, try again ;)

Sutharsan’s picture

FileSize
41.52 KB

Removed quotes from around LANGUAGE_SYSTEM.

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs tests

I think this looks good now.

andypost’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/includes/gettext.incundefined
@@ -80,13 +84,17 @@ function _locale_import_po($file, $langcode, $mode) {
+ * @param $overwrite_options
+ *   An associative array indicating what data should be overwritten, if any.
+ *   - not_customized: not customized strings should be overwritten.
+ *   - customized: customized strings should be overwritten.
  * @param $lang
  *   Language code.
+ * @param $customized
+ *   Whether the strings being imported should be saved as customized.
+ *   Use LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED.

This looks confusing, $customized is just a boolean.
Suppose doc-block should describe deeper what's going to be imported and overridden.

+++ b/core/includes/gettext.incundefined
@@ -395,7 +407,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
-        if (($mode != LOCALE_IMPORT_KEEP) || empty($locale_plurals[$lang]['plurals'])) {
+        if ((array_sum($overwrite_options) > 0) || empty($locale_plurals[$lang]['plurals'])) {

use array_filter() not array_sum()

+++ b/core/includes/gettext.incundefined
@@ -454,15 +467,34 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL
+  $existing_customized = NULL;
+  if ($lid) {
+    $existing_customized = db_query("SELECT MAX(customized) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
+  }
+

use else to define $existing_customized, or just drop definition before if()

Also MAX() need docs!!! Why ? Do we have many translations for one string in one language?

+++ b/core/includes/gettext.incundefined
@@ -479,25 +511,25 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
-      if (!$exists) {
+      if (!isset($existing_customized)) {

seems empty() make more sense

+++ b/core/includes/gettext.incundefined
@@ -479,25 +511,25 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
-      elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+      elseif ($overwrite_options[$existing_customized ? 'customized' : 'not_customized']) {

This kind of arguments is hard to pass from code and harder to read

+++ b/core/includes/gettext.incundefined
@@ -521,13 +553,14 @@ function _locale_import_one_string_db(&$report, $langcode, $context, $source, $t
+  elseif (isset($existing_customized) && $overwrite_options[$existing_customized ? 'customized' : 'not_customized']) {

same

I think this logic should be refactored

+++ b/core/includes/gettext.incundefined
@@ -828,17 +861,67 @@ function _locale_import_parse_quoted($string) {
-function _locale_export_get_strings($language = NULL) {
-  if (isset($language)) {
-    $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language", array(':language' => $language->langcode));
+function _locale_export_get_strings($language = NULL, $options = array()) {

Are you sure that it's easy to rememeber each of this params?

not_ not- - the underscore hard to remember!

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -34,62 +34,80 @@ function locale_translate_import_form($form, &$form_state) {
+  $form['customized'] = array(
+    '#title' => t('Treat imported strings as custom translations'),
+    '#type' => 'checkbox',
+  );

This option really confusing let's hide it into fieldset

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -34,62 +34,80 @@ function locale_translate_import_form($form, &$form_state) {
+  $form['overwrite_options'] = array(
+    '#type' => 'container',
+    '#tree' => TRUE,
+  );

I think better make this fieldset with title Advanced options

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -34,62 +34,80 @@ function locale_translate_import_form($form, &$form_state) {
+    $customized = $form_state['values']['customized'] ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED;

This should be passed from form item, no reason for this logic here, use #return_value

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -118,65 +134,75 @@ function locale_translate_export_screen() {
+    $form['content_options'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Export options'),

content_options?
Advanced [export] options

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -118,65 +134,75 @@ function locale_translate_export_screen() {
+  $language = ($form_state['values']['langcode'] != LANGUAGE_SYSTEM ? language_load($form_state['values']['langcode']) : NULL);

this hard to read - better if() else

+++ b/core/modules/locale/locale.installundefined
@@ -176,6 +176,12 @@ function locale_schema() {
+        'description' => 'Boolean indicating whether the translation is modified.',

s/modified/customized

+++ b/core/modules/locale/locale.installundefined
@@ -485,6 +491,18 @@ function locale_update_8005() {
+function locale_update_8006() {
+  $spec = array(
+    'type' => 'int',
+    'not null' => TRUE,
+    'default' => 0, // LOCALE_NOT_CUSTOMIZED
+  );

add description from schema here

+++ b/core/modules/locale/locale.pages.incundefined
@@ -435,21 +460,23 @@ function locale_translate_edit_form_submit($form, &$form_state) {
+      if (!empty($translation) && ($translation != $value)) {

not need extra braces

+++ b/core/modules/locale/locale.testundefined
@@ -1197,6 +1250,66 @@ EOF;
+   * Helper function that returns a .po file which strings will be marked
+   * as customized.
+   */
+  function getCustomPoFile() {
+    return <<< EOF
+msgid ""
+msgstr ""
+"Project-Id-Version: Drupal 7\\n"

drupal 8 :)

can we re-use already implemented poFiles in tests?

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
44.32 KB

@andypost: attached a patch with some of your notes fixed. Reponse one by one:

1. The customized status bit and the overwrite options are independently evaluated. I've extended / modified the descriptions a bit. Yes, $customized is just a boolean, just like $node->status, but we still have constants for it to have more readable code.
2. Ok, used count(array_filter()) instead of array_sum() > 0, not sure how is this an improvement.
3. Good catch on the MAX(), that seems like was an earlier misunderstanding by roderik on how plurals are implemented (they are not tied to the same lid). Removed the MAX() (and the note on plurals above).
4. !isset($existing_customized) is explicitly used to catch when there was no translation saved. empty() would kick in if the translation was not customized, in which case the db insert would fail. There is a comment there pointing this out, but I've extended the comment above $existing_customized too.
5. On the places where $existing_customized is used in a ternary operator, please suggest a better solution if you have one. Otherwise I think this looks pretty readable.
6. On the _locale_export_get_strings() options, this is a temporary solution and #1189184: OOP & PSR-0-ify gettext .po file parsing and generation is being actively worked on to refactor this whole thing. This makes most of our changes in the .po parsing code at all pretty temporary and I find it hard to justify any need to debate them much.
7. Please read the discussion above. We plan to introduce l10n_update functionality in core (if we have time beyond debating relatively minor code issues). At that point this screen will be likely *mainly* used for importing custom stuff or not used at all. So hiding the functionality that we anticipate most people would come to this screen for sounds pretty bad.
8. Same thing. Extensive discussions about that above.
9. I don't think checkboxes can have a #return_value for their unchecked state. Looks like not. So I don't see how would that solve this.
10. Yes, content_options. It is about what is the content of the exported file. If we call it "export_options" does it tell more? :)
11. Changed to an if/else although I don't think ternary operators are hard to read, they are used plenty in Drupal.
12. Explaining the customized field that it is about marking things that are customized sounds pretty funny. That is not an explanation at all. Modified that to be 'custom to this site' to make more sense.
13. Schema descriptions added to update.
14. Removed extra brace although I believe those enhanced readability :)
15. Changed all .po files to Drupal 8. Yes, there are quite a few .po files in the tests, but they test different things. At this point for example, we need to test both overwrites and new things, so we need some custom tailored .po files for that. You are welcome to open an issue for reworking those .po files in tests if you are interested.

I think this addresses all your concerns in different ways. I do hope we can get this in before 200 comments and maybe can work on something else for Drupal 8 too at some point.

Status: Needs review » Needs work

The last submitted patch, 1445004-102.patch, failed testing.

Gábor Hojtsy’s picture

Status: Needs work » Needs review
FileSize
44.13 KB

Of course if we remove MAX() the return value for the query is becoming different, so what about doing a more traditional object lookup and then checking its emptiness and customized property, which should separate the 'not there' vs. 'not customized' vs. 'there' tri-state.

andypost’s picture

Fixed another array_sum() - it's not good to sum booleans, also array_filter returns array and check for emptiness with count is overhead in function that used in loop is bad idea, I know about #1189184 but this is a really bottle-neck place

Also fixed another Drupal 6 in .po

1445004-opt-105.patch is a bit refactored to reduce a number of queries

Status: Needs review » Needs work

The last submitted patch, 1445004-opt-105.patch, failed testing.

andypost’s picture

Status: Needs work » Needs review
FileSize
1.42 KB
44.42 KB

I've measured performance for array functions

array_filter 0.91011500358582
array_sum 0.61800193786621
andypost’s picture

updated to one query

Gábor Hojtsy’s picture

Status: Needs review » Reviewed & tested by the community

I think those changes look good. Should be back to RTBC then.

andypost’s picture

Suppose we need measures on import with one and 2 queries, most of time system has string in both tables so initial import makes less sense. If one query has better statistics we could backport it to D7

Sutharsan’s picture

I have done performance tests with two scenario's on both patches:

  • Import with 0% overlap
  • Import with 100% overlap

The first scenario is with are strings which do not have a matching source string in the database. e.g. the intial import of a po file. The second scenario is importing string of with all strings have a matching source string in the database. e.g. import the same po file twice. In real live the initial import of a module will have a low overlap (estimated 0 - 20%) sequential imports of updated module updates and import of new module releases will have a high overlap (estimated 80 - 100%).
#108 is approx 5% slower than #107 on imports with 0% overlap
#108 is approx 15% faster than #107 on imports with 100% overlap
During the live time of a site many more imports will be carried out with a high translation overlap rate. Therefore I advise to use #108.

Sutharsan’s picture

Forgot to mention, the above tests were carried out without overwriting existing translations. When overwriting existing translations both patches perform equal.

andypost’s picture

@Sutharsan thanx a lot for testing! 5% slowness on initial import seems depends on mysql settings, less queries should be faster. In case of server on different machine boost should became bigger.

roderik’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
2.89 KB
46.56 KB
99.24 KB

Sorry I'm late to the party :-p I didn't have anything to add to the higher level discussions but should have returned last week...

After a review of the thread I have some minor things (don't worry, you can set to RTBC in no time):

  • Consistency of terms to be as non-confusing to the user as possible. I think they may have been left like this inadvertantly when changing them around and around? Language changed:
    • Treat imported strings as customized translations
    • Overwrite existing non-customized translations
    • Overwrite existing customized translations

    Note: The "Translation type" filter dropdown on the search screen still has options "Base translation" and "Customized translation". I didn't change "Base" back (despite inconsistency) because I really don't know if that would be any better.

  • Re. bottom remark of #85/#86: you can actually hide checkboxes in certain situations, without making things a four step process. You don't need to read through the .po file, only through the existing translations.
  • If no language has been added yet, things default to the first in the list. It's a minor thing, but probably better to not preselect any language so newbies won't create an unwanted language accidentally.

Attached is the view of the import screen on a freshly installed site with no languages enabled and no translations imported. Right after importing your first .po file, one of the two now-hidden checkboxes will appear.

roderik’s picture

...and I've left some other things for a followup issue on purpose, because IANAUIExpert. Feel free to ignore.

I realize that there is no way to explain the import screen fully to everyone without a full man page. But maybe a description with the checkboxes still helps to at least make inexperienced users not feel completely lost?

Or are descriptions that are this large, against UI rules? Probably, huh? (I'm not succeeding in making them any shorter.)

Also IMHO it doesn't hurt to make the first checkbox stand apart from the other two. Because now, users' first impression is "ah, there are 3 on/off options below each other, so 3 options of exactly the same type". Which isn't true. And the fact that the 2nd checkbox does not have a description, helps that.

Also, I see one other thing that might have fallen through the cracks: pixelite's #56 about making the first checkbox into radiobuttons with a Yes/No choice. That would also achieve 'making it stand apart from the other checkboxes'...

Anyway: The attached screenshot is all you need to decide whether you want to continue with #114 or #115.

Gábor Hojtsy’s picture

Status: Needs review » Needs work

I'm starting to loosing faith that we ever going to have this in core. Looks like people have more fun debating how to do it then wanting to see it in. I think the hiding of checkboxes from your patch in #114 make sense, but these make no sense whatsoever:

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -28,8 +28,12 @@ function locale_translate_import_form($form, &$form_state) {
-    $language_options = language_admin_predefined_list();
-    $default = key($language_options);
+    $language_options = array_merge(
+      array('' => t('- Select a language -')),
+      language_admin_predefined_list());
+    $default = '';
+    $required = TRUE;
+    $desc = t('The language will be added automatically.');

This description used to be there but it was removed because it is trivial. Also, WTH include an empty option if that is not valid? What do you do if there is no language set? Is the form label not enough?

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -50,10 +64,12 @@ function locale_translate_import_form($form, &$form_state) {
+    '#required' => $required,

What do you do in the import if there is no language set? Which language do you import the translations to ?!?!

Gábor Hojtsy’s picture

For the second patch, I think your finding on the search screen having wrong wording is spot on, that *should be* fixed. However, I'd prefer if we don't fiddle with this UI anymore, or we'll be in bikeshed land for another 3 weeks, which is likely lot of fun for some, but I'm more interested in delivering solutions for Drupal 8.

+++ b/core/modules/locale/locale.bulk.incundefined
@@ -50,28 +64,34 @@ function locale_translate_import_form($form, &$form_state) {
+    '#description' => t("Until you've actively used the distinction (by e.g. using this import screen), 'customized' is everything entered through the 'Translate' screen. 'non-customized' is everything else."),
...
     '#type' => 'checkbox',
+    '#description' => t("One of the situations where you probably want to check the corresponding 'Overwrite' checkbox, is if you are re-importing an updated .po file which was imported earlier."),
+    '#access' => $existing_c,
   );
 
   $form['actions'] = array(

Can you please leave these two alone and open followups if you have concerns? Unless you want another 3 week bikeshed about this, that is. :/ :(

Gábor Hojtsy’s picture

In other words, I'm posting the #118's comment. This is a clear WARNING sign, especially it is all in less than 2 months. We can clearly debate the UI indefinitely. We arrived at the conclusion that this is going to be an advanced UI once/if we reach the inclusion of l10n_update in core. If we keep debating this one, we lock our energies down here instead of working on more useful stuff. However, if we get the feature in with the current UI proposal, you can all be absolutely free to go back and debate the UI however long you want while others can work on actually building on this functionality and continuing the inclusion of l10n_update in core. If we don't get this patch in, we are locked in this debate and will not reach any useful goals anytime soon. Not good for anybody, unless you are looking for a meaty bikeshed. This issue was supposed to be simple and clearly not something that would take us two months to fix. If we fix such small issues with this pace, we can fix 3 more before Drupal 8 is frozen. Clear?

Sutharsan’s picture

Status: Needs work » Needs review
FileSize
45.37 KB

I very much agree with Gábor and will therefore not comment on #114 - #117.

Filter select list modified to 'Non-customized translation' (other options unchanged: 'All' and 'Customized translation')
Patch based on #108 (1445004-opt-107.patch) as is was the better performing one.

roderik’s picture

Status: Needs review » Reviewed & tested by the community

Also, WTH include an empty option if that is not valid?

To prevent an invasive action (creating a wrong language) from taking place if you didn't select anything.

What do you do in the import if there is no language set?

You don't do anything in the import. You throw a validation error, that's what you do. Because of above.

Hey, I'm just answering your questions. As reference for the followup issue.

I'm sorry. I meant to only sweep through the last 50 comments to see how things have evolved, and meant to split out the 'trivial' stuff from the 'non trivial' stuff, that's why I created two patches. Apparently the notion of what is 'trivial' is something I should still get a hold of.

Reviewed Sutharsan's patch.

Gábor Hojtsy’s picture

Thanks. Please let this get in and fiddle with the rest in followups. RTBC to commit can still easily be 1-2 weeks. :/

Dries’s picture

Status: Reviewed & tested by the community » Needs work

This is a great feature, and one that is much wanted I'm sure. Committed to 8.x.

I think it warrants a CHANGELOG.txt entry; let's add one?

Gábor Hojtsy’s picture

Title: Implement customized translation bit on translations » Changelog entry for "Implement customized translation bit on translations"
Status: Needs work » Reviewed & tested by the community
FileSize
728 bytes

Here it is. Should be simple to get in.

Gábor Hojtsy’s picture

(BTW added change notice node at http://drupal.org/node/1525160 too).

Dries’s picture

Assigned: Sutharsan » jhodgdon

Moving to Jennifer's queue.

jhodgdon’s picture

Title: Changelog entry for "Implement customized translation bit on translations" » Implement customized translation bit on translations
Assigned: jhodgdon » Unassigned
Status: Reviewed & tested by the community » Fixed

Looks good -- changelog entry has been committed.

Gábor Hojtsy’s picture

Issue tags: -sprint

Thanks, off sprint then.

Automatically closed -- issue fixed for 2 weeks with no activity.