-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
Extend translation linting to check translation text fits within widget bounds #21293
base: bleed
Are you sure you want to change the base?
Conversation
// HACK: We hardcode a list of the attachments made via code, so we can verify the sizes of the child within their parent container. | ||
static readonly Dictionary<(string Logic, string ParentWidgetId), string[]> KnownParentChildWidgetIds = new() | ||
{ | ||
{ ("LoadIngameChatLogic", "CHAT_ROOT"), new[] { "CHAT_PANEL" } }, |
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'm wondering if it would be cleaner to pull these out into a yaml file?
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.
An easier and IMO better approach would be to move it onto the chrome logic, like we do with hotkey names.
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.
Yes having an attribute that allows logic classes to specify the dynamic parent->child relationships they are going to set up would be the scalable solution here. Some of those are buried deep in helper methods though, so I don't think it'd be a quick refactoring for all cases.
Would we be okay to defer that as a follow-up, or should we get that in as a pre-req 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.
I'm fine with it being a followup.
// HACK: Hardcode how each widget determines available fonts. | ||
var fontName = nodeType switch | ||
{ | ||
"Button" or "DropDownButton" or "Checkbox" or "MenuButton" or "WorldButton" => node.Value.NodeWithKeyOrDefault("Font")?.Value.Value ?? ChromeMetrics.Get<string>("ButtonFont"), |
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.
Would it make sense to add a FontReference
attribute that we can reflect over instead? If a widget has multiple text entries with different fonts the attribute could point to the text it applies to.
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.
Yes, that'd be the scalable approach to solving this. I don't think I ran into anything that uses multiple fonts/texts but yes we could encode that too.
Compared to the relationships, this attribute would be easier to get set up.
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.
But not that easy, since we need the default values from the class and we're not actually instantiating the widgets. :/
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.
Looking up the translation values everywhere in the classes is a bit messy. Having an underlying layer take care of it would be cleaner.
The translation is now showing up everywhere in each constructor mixing with logic.
Pseudo code
class SomeWidget
{
struct LocaleResource
{
// Would lookup "Scope.SomeWidget.buttonCaption1" from a resource file.
[LocaleString id="button1Caption"]
string buttonCaption;
}
[LocaleResource id="Scope.SomeWidget"]
static LocaleResource locale;
static LocaleResource GetLocale(Mod mod, bool force = false)
{
var mod = mod || GetDefaultMod(mod);
// init once
return locale;
}
void SomeMethod()
{
var buttonCaption = GetLocale(GetModFromSomeWhere()).buttonCaption;
}
}
This would then also allow you to load resources without instantiating the class. I.e. CurentNamespace.Locale.SomeWidgetLocaleResource.cs
.
You could even pull the struct outside the class, put all in a Locale namespace. Which your linter could scan.
Also avoid loading translation with each instantation of i.e. widgets. Loading of widgets is already quite dynamic and slow.
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 think you've effectively described the existing translation setup? We're already able to pull the translation text successfully without having to instantiate widgets. It's other references like the fonts and widget IDs that don't have such a mechanism and can't easily add one since their data might be set via code. Whereas translation config is defined in YAML only.
I think the outcome here is that the PR as it stands is pretty much what we're going to get for a "quick win". Replacing the hacks with a proper solution would be a non-trivial undertaking. So I'd say review this on the basis of whether you think these value justifies the amount of hacks required.
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 this is mergable, for next release we need to make sure translations have proper third party support, and this PR will only delay us from reaching that milestone. If noone's gonna be unhardcoding this lint test then I think a second PR should be split from this with the yaml changes
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.
Having the lint pass detect some size issues for you still seems to be a net win to me compared to not detecting anything at all. But split out #21309 so we can at least get that merged.
8149a2d
to
4b18472
Compare
4b18472
to
f746e3e
Compare
f746e3e
to
b5acbf6
Compare
b5acbf6
to
63ef39c
Compare
Have added a commit that allows unhardcoding the parent->child widget relationships. However the code quality is atrocious and there's still a lot of TODOs so converting to a draft PR. |
63ef39c
to
4c0252a
Compare
99f2456
to
1c3b32c
Compare
Ok, updated:
I haven't dealt with unhardcoding all the smaller hacks, but this deals with the main one by allowing the entire widget tree to be discovering without hardcoding it in the linter. |
505d6e7
to
186fde0
Compare
I've split the changes not related to checking the text size into #21389, so we can get the other accumulated improvements in on their own merit. |
186fde0
to
8272060
Compare
…et bounds. We teach the CheckTranslationReference lint pass to determine the widget bounds of all possible widget trees, and then check the translation text fits within the bounds where it is going to be rendered. This allows translation text that is too long to be flagged, so that either the translation text can be revised or the UI adjusted to ensure the text fits. As the lint pass is doing a static analysis, results won't be perfect as for example it won't replicate logic that resizes widgets dynamically at runtime. It also hardcodes a lot of knowledge about the function of widgets and logic classes in order to provide a sufficient model for determining widget bounds so we can check sizes. To capture some of the relationships created at runtime, the new DynamicWidgets class allows ChromeLogic classes to encode the windows and parent->child relationships they intend to establish. Existing issues flagged by this pass often look okay in the UI, as we allow text to be rendered outside of the available widget bounds. So the text can appear to be fine even though it's outside the defined area. By fixing the existing sizes in YAML, we now have an English translation that pass the lint. Future translations will now be able to quickly identify issues as we know the English version passes. (This is also useful to help avoid issues where the bounds do matter, e.g. the scroll widget uses the bounds to decide what is in view and needs rendering, so inaccurate bounds can cause items to fail to render when required). Fixes detected by this lint pass were merged in ca6aa5e. To try this lint pass, run the Utility with `ra --check-yaml`, `cnc --check-yaml`, `d2k --check-yaml` or `ts --check-yaml` to validate that mod.
8272060
to
d588c4a
Compare
With #21389 merged, the diff in |
We teach the CheckTranslationReference lint pass to determine the widget bounds of all possible widget trees, and then check the translation text fits within the bounds where it is going to be rendered. This allows translation text that is too long to be flagged, so that either the translation text can be revised or the UI adjusted to ensure the text fits.
As the lint pass is doing a static analysis, results won't be perfect as for example it won't replicate logic that resizes widgets dynamically at runtime. It also hardcodes a lot of knowledge about the function of widgets and logic classes in order to provide a sufficient model for determining widget bounds so we can check sizes.
Existing issues flagged by this pass often look okay in the UI, as we allow text to be rendered outside of the available widget bounds. So the text can appear to be fine even though it's outside the defined area. By fixing the existing sizes in YAML, we now have an English translation that pass the lint. Future translations will now be able to quickly identify issues as we know the English version passes. (This is also useful to help avoid issues where the bounds do matter, e.g. the scroll widget uses the bounds to decide what is in view and needs rendering, so inaccurate bounds can cause items to fail to render when required) Fixes detected by this lint pass were merged in #21309.
To try this lint pass, run the Utility with
ra --check-yaml
,cnc --check-yaml
,d2k --check-yaml
orts --check-yaml
to validate that mod.Assists with #10475.
Example by setting
button-main-menu-singleplayer = SingleplayerButTheTextIsTooLong
:OpenRA/mods/common/languages/chrome/en.ftl
Line 325 in 0741439
Ingame:
Utility output of
ra --check-yaml
: