-
-
Notifications
You must be signed in to change notification settings - Fork 159
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
feat(translation): using multilanguage features with Serenity/JS #1135
base: main
Are you sure you want to change the base?
Conversation
@jan-molak Not even sure, if you get informed by mail when just a draft for a PR is created. |
Hey! Thanks for the ping, no, GitHub doesn't seem to send notifications for draft PRs
Yes, I'd approach it this way too. I like the idea of using an ability for that; it has to hold some state, so this sounds like a good way to do it. For example, given our actor had an ability to
Where
I think injecting a dictionary might be easier than providing each translation individually, since this way it could be stored in a JSON file or sth like this. Now, assuming that the website is in
which offers good flexibility since we don't need to care about the language the website is currently in. Or do it on the expectation side too if we wanted:
We could also have a way to specify a "default" language when defining the ability
Then it could be simplified further:
Using Let me know what you think, it seems like a really good feature to add to |
Agreed.
Agreed. I assume my examples in my test specs are a bit misleading. The reason why I would like to use an array of dictionaries: I still have a external Serenity/JS library which servers shared I have (for now) two separate test projects for different modules. Both module use the "outer" application framework and therefore make use of the common library - and the common translations. But each module has its very specific set of translations (or even each task or page elements class). So with an array of dictionaries I can inject more than just one object. Also agreed, that it could be some JSON. I would even like to see some typed dictionary because it's easy to mess up with the translation strings, so an IDE support would be nice. But maybe your approach will mitigate this problem a bit. Okay, so let's talk about the nature of the dictionary later. I got another point I struggle with. So, given, we have an import { Ability, AnswersQuestions, UsesAbilities } from '..';
import { LogicError } from '../../errors';
import { formatted } from '../../io';
export class Translate implements Ability {
constructor(private readonly dictionaries: Array<unknown>, private readonly locale: string) { }
private translations: unknown;
static as(actor: UsesAbilities & AnswersQuestions): Translate {
return actor.abilityTo(Translate);
}
static using(dictionaries: Array<unknown>, locale: string) {
return new Translate(dictionaries, locale)
}
translate(translationString: string): Promise<string> {
try {
return this.translations[translationString][this.locale] ?? [translationString][this.translations['en_US']];
} catch (error) {
throw new LogicError(formatted`Translation error for "${translationString}" and locale "${this.locale}": ${error}`)
}
}
} The pure logic - in my example the Sorry for still not having a complete picture of the framework. |
Thinking about it, rather than hand-rolling our own standard, perhaps we could/should consider some XLIFF-compatible "shape" for the dictionaries. Many (most?) translation tools are compatible with it, and there's an NPM xliff module that we could use to transform dictionaries to and from XLIFF. I think often when the system under test is translated by a 3rd party, translations might be provided in this standard, so Serenity/JS could load a dictionary that the main system is using... I'm not sure if that's a good idea or not yet, just thinking aloud really. I'm not set on XLIFF either; it's something I've used in the past in a similar context. There might be other / more popular alternatives?
Yes, pretty much. An ability would handle the dictionaries and retrieve/lookup specific translations. The question would be just an adapter between abilities and anything that needs that data. |
7ee27ff
to
cbf7846
Compare
Following your suggestions, I've created a draft of an Don't know why SauceLabs Pipeline fails, probably some permission issues.
I was not aware that there is a standard for this. Before I started with my own But than I found, that all those things would be over the top for my usage. As I separate some shared translations from the module specific translation and as I won't check every possible translation in the UI, my lists stay rather small. Then I thought of my fellow testers, who would have to learn the next fancy syntax how to get things translated. This is where I appreciated the simple
This was something I first thought about as well. Well, not exactly the same way, not the way with the XLIFF files. But why not use the string lists from the application (as most of the strings would/should be dynamic translations as well. Again, this was a bit over the top, given the thoughts I shared before. Last but not least, I tried to reduce npm dependencies instead of increasing them, lately ... Maybe the current solution of the But as Serenity/JS is becoming an "agnostic" tool, maybe there would be an extendible approach as well. actor.whoCan(Translate.using('XLIFF').with(<<whatever would be necessary here>>));
// or
actor.whoCan(Translate.using('CustomFormat').with(<<whatever would be necessary here>>)); EDIT: I also noticed I would need a Then I added a question that returns the locale current locale of the |
f47ffd2
to
2b5b9d9
Compare
Hi @jan-molak, what do you think about this (last comment) ? In addition, I noticed some unexpected behaviour: How are the names of the actors connected to the actors I engage? Though I engage new actors before each step in the specs "Heidi" answers with 'de_CH' no matter if I put her in the UsAmerican or in the Swiss German spec? I thought this were just strings for the reporting and to be able to distinguish the actors in the test specs, but now it seems, as they where somehow tied to the Or do I have some problems in my |
Looks like we need to limit running sauce tests to pull requests and branches originating from the main monorepo. Let me see if I can fix that.
Yeah, that's a good point. You could see it work similarly to
That's right, each The reason for this is so that people could write Cucumber scenarios like this: Given Heidi ...
When Heidi ...
Then Heidi ... where each Cucumber step references the same actor by their name. Does this make sense? Also, I wonder if |
because they won't have access to saucelabs username and access key and will therefore always fail re #1135
I think it should be fixed now on |
Thanks, I will do this as soon as possible.
What do you have in mind? Something like that? Just provide a method in |
2b5b9d9
to
ae7163b
Compare
Fixed. |
This PR is just meant to start a discussion about this topic.
My main use case is having an application that supports multiple user languages. While the use cases stay the same, we have to locate page elements by a text in a different language or ensure, that the text of a page element is displayed in the right language.
Until now I have a
translateTo
function which takes an array of objects like(See Gitter discussions, https://gitter.im/serenity-js/Lobby?at=611662b9f31bc0605a5e25b0)
I have a global configuration file, where the global locale for all test runs is set. But the requirement is to be able to set another locale for a certain test spec only, as well. (At the moment, this is just a simple reset of the locale from the global config object within the spec.)
Normally I’d put my
Task
s andPageElements
into classes. I try to separate my tests:Task
s that are imported from myTask
classes. I try not to import anyPageElements
into the spec files directly.Task
classes I import from thePageElement
classes.Having the requirement to be able to change the locale from a certain test spec (and for this test spec only) I have to “inject” the locale into the
Task
class constructors and then into thePageElements
class constructors as well and init thetranslateTo
function with the right locale (and set of translation objects).Hence, I cannot really use static
PageElements
or constants as this translate object has to be initialised each time before I can use it in a locator strategy like.where(Text, t(‘SAVE’))
for example.So every time I have to import from a
Task
class in my specs I have to create a new instance in of it, what could become quite nasty when importing from more classes. Every time I write a newTask
orPageElement
class I have to ensure, that the translate object is initialised.I tried to think my use case the Serenity/JS way. Isn’t it the
Ability
of anActor
toSpeakALanguage
? Would it not be a an interaction toLearn.newVocabulary(<<object>>)
and aQuestion
toTranslate.usingPlaceholder(‘SAVE’)
I started an experiment which already is functional, but I still have problems to use this in some locators, but I think I messed something up with return types, we can discuss this later.
Before I continue I’d like to get you opinion if I’m on the right path and if such a thing would be worth a contribution to Serenity/JS?