-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
gui: Allow to translate action and type in Recent Changes modal #8548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gui: Allow to translate action and type in Recent Changes modal #8548
Conversation
Currently, action and type are both displayed only in English. This commit makes it possible to translate both of them. Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
ac3fe55
to
a56bd8c
Compare
@@ -17,8 +17,16 @@ | |||
<tr ng-repeat="changeEvent in globalChangeEvents"> | |||
<td ng-if="changeEvent.data.modifiedBy">{{friendlyNameFromShort(changeEvent.data.modifiedBy)}}</td> | |||
<td ng-if="!changeEvent.data.modifiedBy"><span translate>Unknown</span></td> | |||
<td>{{changeEvent.data.action}}</td> | |||
<td>{{changeEvent.data.type}}</td> | |||
<td ng-switch="changeEvent.data.action"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be easier to treat these as keys directly? Just like we do for the theme-name-*
strings. Like this:
<td>{{("change-action-" + changeEvent.data.action) | translate}}</td>
That would allow some more context to distinguish from other uses of the single words.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tried to make this work, but for me, it seems that a clean and non-hard-coded solution without fiddling around with JS could be to allow syntax like the one described at https://toptal.com/angular-js/internationalize-your-angularjs-app, which in HTML is
<span translate="change-action-modified">modified</span>
<span translate="change-action-deleted">deleted</span>
and in JSON
"change-action-modified": "modified",
"change-action-deleted": "deleted",
and so on. The current translate.go
script doesn't support it though. I've tried to incorporate the syntax into the script, but my Go skills and knowledge are extremely rudimentary, so I've only managed to go as far as save the value to a variable when it's non empty. Not really sure where exactly it should be passed then and added to json as name though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well it's not impossible to add strings to the translation template without help from the script. Apparently it does catch the theme names, so maybe have a look at how that's done (I don't remember now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The theme names are actually a combination of Go, JS, and the actual filenames, so it's quite different than this one. Here, I only want to let the script accept <div translate="key">value</div>
syntax universally 😉.
I've now given this a try and modified the script code to use translate
attribute value as key name when present. Please have a look and let me know if this is the right way to go.
Also, do you think that the two other strings, i.e. file
and folder
should also follow the same change-type-*
pattern, or is it OK to use them as is?
Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, do you think that the two other strings, i.e. file and folder should also follow the same change-type-* pattern, or is it OK to use them as is?
IMHO they should be treated the same as change-action-*.
I'll let others chime in on the question whether some translation keys in lang-en.json
that won't get regenerated by subsequent translation script runs are acceptable. If so, I'd err for a simpler solution.
script/translate.go
Outdated
@@ -102,12 +106,16 @@ func inTranslate(n *html.Node, filename string) { | |||
} | |||
} | |||
|
|||
func translation(v string) { | |||
func translation(v string, k string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need a separate key here. The v
argument holds the key, which also serves as the initial translation unless there is a better one.
<span ng-switch-when="modified" translate="change-action-modified">modified</span> | ||
<span ng-switch-when="deleted" translate="change-action-deleted">deleted</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little too much repetition for my taste. You need a separate branch for every possible value, plus an entry in the language file, and finally it's unclear why the string reads "modified" when the translation key is "change-action-modified". If we're going to use abstract key strings, the (English) translation should live in the JSON file just like for other languages.
My proposal avoids all that complexity, with the only downside that not necessarily all keys are gathered from the source. But what's already in lang-en.json
won't get lost by running the translation script anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and finally it's unclear why the string reads "modified" when the translation key is "change-action-modified". If we're going to use abstract key strings, the (English) translation should live in the JSON file just like for other languages.
I'm not sure if I understand this one. How is
"change-action-deleted": "deleted",
"change-action-modified": "modified",
different than what's already used for themes?
"theme-name-black": "Black",
"theme-name-dark": "Dark",
"theme-name-default": "Default",
"theme-name-light": "Light",
Do you mean that the key names are too cryptic?
My proposal avoids all that complexity, with the only downside that not necessarily all keys are gathered from the source. But what's already in
lang-en.json
won't get lost by running the translation script anyway.
But the ability to regenerate from scratch fully automatically is the big advantage (and arguably the main point) of the script… I don't really think it's worth to break it to add a few hard-coded lines…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the theme names, the key is derived from the directory name, and the latter (capitalized) is used as initial translation. That happens to be the same as the English translation value for the key. But it's certainly possible we might want to set the English value to something else in the future. For example we've seen "master" being widely replaced by "main" for sociological reasons. Or a typo might be found in the English string. Both would be reasons to adjust lang-en.json
, but keeping the key (and thus the source file) untouched in order to not cause retranslation in all languages. That's the reason (IIUC) why the translation(v string)
function does not overwrite messages which already exist in the input (lang-en.json
).
Your solution puts both the key and the English translation inside the source HTML file. While the purpose of the key is having a unique reference to which translation is needed. The English translation does not need to live there separately.
But the ability to regenerate from scratch fully automatically is the big advantage (and arguably the main point) of the script…
IMHO the main point is updating translations from the source. Since we do keep lang-en.json
in version control, there's nothing wrong with making manual changes to it, despite having a helper script for automating the most common updates, which respects those manual adjustments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I don't really see anything wrong in keeping everything in the HTML… Isn't this how all existing translations (but themes) work in Syncthing currently? Meaning, all strings live in HTML and JS, and the JSON files can all be re-created 100% automatically.
The only reason for making the keys different from values in this particular case is to avoid re-using the single words in unrelated places, isn't it? I understand that ideally it would be better to separate (English) translations from the code, but currently both HTML and JS are English first, and translations come second… Isn't it a bit too late for changing that?
IMHO the main point is updating translations from the source. Since we do keep lang-en.json in version control, there's nothing wrong with making manual changes to it, despite having a helper script for automating the most common updates, which respects those manual adjustments.
Not really a fan of doing manual changes to the JSON files, to tell the truth… This would also make it impossible to easily distinguish between current and obsolete strings, wouldn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really a fan of doing manual changes to the JSON files, to tell the truth… This would also make it impossible to easily distinguish between current and obsolete strings, wouldn't it?
The HTML and JS files contain keys, not English translations. Although they do usually match, it's a semantic difference for the reasons given above. The reason why we should have different key and English translation in this case is because the key is actually a programmatic value coming from the backend, not meant as a GUI label, just like the theme directory names. It doesn't mean we need to separate key and (English) value for all other cases.
If the lang-en.json
file could definitely always be completely regenerated, there would be little reason to keep it versioned. You don't need to do manual changes there, as the script will still pick up new stuff. If you want to to weed out obsolete strings, you still can in the same way by emptying it first. But before committing, you should review the changes to only remove strings that are definitely no longer in use.
Yes, there are manual steps involved, but only for rather uncommon tasks. By trying to make everything automatic, you lose the ability to manually override when needed (which we have now). And bending over backwards to adjust the automation for every special case that comes along is not worth the added complexity. Just utilize the flexibility we have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, for me it still seems to be more of a debate between "flexible yet fragile" and "inflexible but automated". I personally always prefer automated solutions over something that can easily break due to user error (e.g. if someone removes the hard-coded strings, maybe considering them obsolete, or by mistake, or simply not knowing that the file can't be generated from scratch with the script). On top of that, I think the point about being unable to distinguish between used and unused strings is still relevant too.
That workflow is based on the assumption that regenerating is always completely possible from an empty state, which is not unconditionally true.
Well, it's not written anywhere, but that is what seems to have always been the case here since the very beginning. There's literally not even one line in the JSON file that can't be automatically generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On top of that, I think the point about being unable to distinguish between used and unused strings is still relevant too.
In general I agree with you that automation is a good thing to avoid human errors. We can build more automation, trying to make that distinction without human consideration. But in this case, the involved complexity outweighs the benefit. It goes like this: You regenerate from scratch, see that some change-action-...
lines were removed, grep for change-action
and find the place where they are used and why they're not put back automatically. Then you discard that change before committing. Which happened roughly once in the history of Syncthing so far (#8288).
That workflow is based on the assumption that regenerating is always completely possible from an empty state, which is not unconditionally true.
Well, it's not written anywhere, but that is what seems to have always been the case here since the very beginning.
It was always possible to regenerate from the checked in lang-en.json
file, exactly because existing strings were not discarded. I didn't make that decision, but it looks like intentional behavior in the code.
There's literally not even one line in the JSON file that can't be automatically generated.
That's only true since you argued to remove them in #8288, before it has not always been the case. And if we go the route of manual additions for this PR, it will not be again, which is also acceptable. Whoever, for whatever reason, tries to clear out that translation file (which is usually unneeded), will see that some lines don't come back and better be ready to invest some time figuring out why (actually removed or just not auto-extracted). I for one will watch out for commits that remove translation strings for no reason.
This PR is a Good Thing (thank you) and shouldn't be held up by fundamental discussions. Let's aim for less complexity unless there is a clear, large benefit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for the record, removing unused strings wasn't my idea. #8288 was only a follow-up to ec718e7 (which is actually linked to from one of the comments in #8288). The example to remove them had already been set before.
I understand that cluttering the HTML may not be the best method, but why not put everything in the translation script then? This will allow to keep the HTML clean while still not having to add any hard-coded lines to JSON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it is hard to find and not necessary. Again, there's nothing wrong with having manual additions in the JSON file. If it was a purely generated file, we wouldn't need to track it in Git.
The proper solution to this whole thing would be to extend the translations to cover the backend code as well. There are more examples IIRC of English phrases that might get passed to the world untranslated. Even taking aside the CLI strings and log messages. But that's definitely not in scope for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, some research turns out that i18n for a golang app is rather complicated compared to our current system. Tested both the gotext
and goi18n
packages, but no quick and easy solution to just mark some strings that should be extracted for translation. These packages would allow adding proper translations to more parts of the backend code later. Some custom solution could be cooked up, but again it's not really worth the effort just to get these automatically put into the GUI strings JSON resource.
Another approach would be to use the translation as suggested above (without ng-switch
). Then put some $translate.instant()
calls into a JS function that never gets to run, just for the script to pick up expected values that should be translated. Could be hidden inside a function along these lines:
$scope.changeEventAction = function (changeEvent) {
return $translate.instant('change-action-' + changeEvent.data.action);
// Only for extracting expected translation keys:
$translate.instant('change-action-modified');
$translate.instant('change-action-deleted');
};
This would lead to the initial translation being identical to the key though, when regenerating from an empty lang-en.json
file. Instead the fall-back value should be the action itself (the single English word from the API response). Which, again, is drop-dead easy when just adding it to the JSON file directly.
I stopped reading the lengthy exchange at some point. Is it correct to say a huge can of worms was opened over having a separate translation for "modified" anywhere versus "modified" as in the file change action? If that's the case, is it really worth opening that can over this distinction (as in I don't really see it's point, what is it?). |
That's not really my pain point at least. I dislike the current state where the HTML line contains almost the same value three times (switch, translation key and another translation) where just passing the variable to the translate function once would be enough. If we want to have keys differing from the initial English translation, I think it's still not worth the added complexity of adding different parsing rules. The goal of being able to completely automatically regenerate |
Well, to be specific, the original PR looked like this, without any translation keys. The
|
Ok, so options are a) using the event type and action directly as translation keys and b) handle actions/types explicitly which means having "deleted" et. al. twice on a line (above/first commit). Personally I prefer the simple, explicit and automated option with inline duplication over the concise and manual one. |
It's a little bit more complicated 😉. Three solutions have been proposed actually.
HTML:
and JSON generated automatically with
This is the simplest solution, but problems may arise when the same single words are re-used in other places in a different context.
HTML:
and JSON still generated automatically with
This solution is similar to the first one, albeit more verbose, but it does solve the problem of re-using the same single words in other places.
HTML:
and JSON modified manually.
This solution is very concise. However, it requires essentially hard-coding the strings in JSON instead of using the The main point of the lengthy discussion was that I didn't want to add the hard-coded lines, so that we could still use |
Ah well I believed the key names mattered in transifex, but I see now they actually don't. So my original concern over "change-action-modified" being translated literally doesn't apply. Anyway, still the main difference is more concise html vs being able to autogenerate translations, right (with a variation on key names)? And in the concise version a missing translation key would be displayed in the GUI as "change-action-missing" instead of just "missing", right? |
The original sounds fine to me. We have a history of blowing away lang-en.json and trusting it to be regenerated, and even if we don't do that very often it seems like a property it might be worth spending at least minimal effort to retain. |
Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
I have now reverted everything to the original PR 😵. |
Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
* main: (69 commits) Handle relay connect timeout (fixes syncthing#8749) (syncthing#8755) gui, man, authors: Update docs, translations, and contributors build: Go 1.19.5 gui, man, authors: Update docs, translations, and contributors script: Add weblatedl.go for downloading updated translations (syncthing#8723) gui: Allow to translate action and type in Recent Changes modal (syncthing#8548) gui, man, authors: Update docs, translations, and contributors gui: Fix undefined lastSeenDays error in disconnected-inactive status check (ref syncthing#8530) (syncthing#8730) gui, man, authors: Update docs, translations, and contributors gui, api: Indicate running under container (syncthing#8728) lib/fs: Use io/fs errors as recommended in std lib (syncthing#8726) build: Handle co-authors (ref syncthing#3744) (syncthing#8708) lib/fs: Watching is unsupported on android/amd64 (fixes syncthing#8709) (syncthing#8710) lib/model: Only log at info level if setting change time fails (syncthing#8725) lib/model: Don't lower rescan interval from default on auto accepted enc folder (fixes syncthing#8572) (syncthing#8573) gui, man, authors: Update docs, translations, and contributors gui: Remove unmaintained language variant nl-BE (syncthing#8722) gui, script: Fix indentation in lang-en.json to match others (syncthing#8721) docker: Ensure entrypoint is executable (syncthing#8719) Go 1.19.4 ...
Currently, action and type are both displayed only in English. This commit makes it possible to translate both of them.
Signed-off-by: Tomasz Wilczyński twilczynski@naver.com
Screenshots