Localization API

Development

Sorunome

2 months ago

So, the idea is to add a simple API for games to be localized, so that there is less of a language barrier. This would require a setting in the home menu so that the user can pick the language. Now we want the programmer to make it as easy as possible to use different languages, this is what i came up with what the programmer would have to do:


/*
 i send you this block for reference of how stuff might work internally
*/
enum class Lang_Code : uint8_t {
	de,
	en,
	// the entire list...no worries, this is evaluated at compile-time https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
};
struct Lang_Entry {
	const Language lang;
	const Lang_Code code;
	const char* str;
};


/*
 this block is what the user would write, anywhere in the sketch is fine
*/

enum class Text {
	Welcome,
	Something_else,
	The_Game
};

const Lang_Entry asdf[] = {
	{ Text::Welcome, Lang_Code::en, "Welcome!" },
	{ Text::Welcome, Lang_Code::de, "Willkommen!" },
	{ Text::Something_else, Lang_Code::en, "Something else!" },
	{ Text::Something_else, Lang_Code::de, "Etwas anderes!" },
	{ Text::The_Game, Lang_Code::en, "The Game!" },
	{ Text::The_Game, Lang_Code::de, "Das Spiel!" },
};

gb.language.set(asdf);

// somewhere in code
gb.language.print(Text::Welcome);
// somewhere else
gb.display.print(gb.language.get(Text::Something_else));

As charset we could use https://en.wikipedia.org/wiki/ISO/IEC_8859-1 as that is still one-byte and thus we can still use char-arrays

What do you guys think about this? Does it seem intuitive enough to use?


EDIT: Or would you prefer something like this?

/*
 i send you this block for reference of how stuff might work internally
*/
enum class Lang_Code : uint8_t {
	de,
	en,
	// the entire list...no worries, this is evaluated at compile-time https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
};
struct Lang_Entry_Single {
	const Lang_Code code;
	const char* str;
};
struct Lang_Entry {
	const Language lang;
	const Lang_Entry_Single entries[4];
};


/*
 this block is what the user would write, anywhere in the sketch is fine
*/

enum class Text {
	Welcome,
	Something_else,
	The_Game
};

const Lang_Entry asdf[] = {
	{ Text::Welcome, {
		{ Lang_Code::en, "Welcome!" },
		{ Lang_Code::de, "Wilkommen!" },
	}},
	{ Text::Something_else, {
		{ Lang_Code::en, "Something else!" },
		{ Lang_Code::de, "Etwas anderes!" },
	}},
	{ Text::The_Game, {
		{ Lang_Code::en, "The Game!" },
		{ Lang_Code::de, "Das Spiel!" },
	}},
};

gb.language.set(asdf);

// somewhere in code
gb.language.print(Text::Welcome);
// somewhere else
gb.display.print(gb.language.get(Text::Something_else));

I see clear advantages in this second approach, both for how to implement it in the background and for the end-user using it, since it has more structure

Drakker

NEW 2 months ago

The first option is a bit simpler, it gets my vote.

Sorunome

2 months ago

The thing i'm a bit worried about the first option is that something like this is also valid:

const Lang_Entry asdf[] = {
	{ Text::Welcome, Lang_Code::en, "Welcome!" },
	{ Text::The_Game, Lang_Code::de, "Das Spiel!" },
	{ Text::Something_else, Lang_Code::en, "Something else!" },
	{ Text::Something_else, Lang_Code::de, "Etwas anderes!" },
	{ Text::Welcome, Lang_Code::de, "Willkommen!" },
	{ Text::The_Game, Lang_Code::en, "The Game!" },
};

Or, well, scramble the entries in any other way. With the second option we at least force the user to maintain some structure, as this unstrucutred stuff could become quickly a mess.

Performance-wise the second one would be a tad better, too, (because we already have some structure), but the difference will probably be unnoticable.

OFC I'd implement the first one if it gets voted etc., that is the whole point of discussing the API in here ^.^ Any other suggestions are welcome, too.

Sorunome

NEW 2 months ago

Drakker Drakker

The thing i'm a bit worried about the first option is that something like this is also valid:

const Lang_Entry asdf[] = {
	{ Text::Welcome, Lang_Code::en, "Welcome!" },
	{ Text::The_Game, Lang_Code::de, "Das Spiel!" },
	{ Text::Something_else, Lang_Code::en, "Something else!" },
	{ Text::Something_else, Lang_Code::de, "Etwas anderes!" },
	{ Text::Welcome, Lang_Code::de, "Willkommen!" },
	{ Text::The_Game, Lang_Code::en, "The Game!" },
};

Or, well, scramble the entries in any other way. With the second option we at least force the user to maintain some structure, as this unstrucutred stuff could become quickly a mess.

Performance-wise the second one would be a tad better, too, (because we already have some structure), but the difference will probably be unnoticable.

OFC I'd implement the first one if it gets voted etc., that is the whole point of discussing the API in here ^.^ Any other suggestions are welcome, too.

Drakker

NEW 2 months ago

Users writing messy code is their own problem, not your's. ;)


Can you do a quick speed test and see if there's any difference?

Rodot

2 months ago

I don't think performance is an issue at all here ^^ Premature optimization again hahaha

Sorunome

2 months ago

Users writing messy code is their own problem, not your's. ;)      

It will be mine once they ask for help on the forums....

Can you do a quick speed test and see if there's any difference?

It would be highly dependant on how many different text things you defined

Rodot

NEW 2 months ago

Drakker Drakker

I don't think performance is an issue at all here ^^ Premature optimization again hahaha

Rodot

NEW 2 months ago

I think I would prefer the second option too, looks "cleaner".

Why does the use have to declare the enum class Text though, it can't be in the library ?

But I'm thinking this syntax is pretty complicated for newbies, like, really (3 layers of { }, objects :: and all). So we consider newbies won't do multi-lang support anyway then ? Or would it possible to have the same structure, but with a simplified user-side usage ? Using only syntax and symbols they already know ?

I know it's crap and the syntax is wrong, but just to show you the idea:

const MultiLang myText[] = {
  {LANG_EN, "Hello"},
  {LANG_FR, "Bonjour"},
}

const MultiLang myOtherText[] = {
  {LANG_EN, "High scores"},
  {LANG_FR, "Meilleurs scores"},
}

gb.display.print(myText.get());
gb.display.print(myText.get(LANG_FR));

Until we decide we can already make the global language setting available and let people free to do their own implementation. A language API isn't the priority _at all_ right now

Sorunome

2 months ago

I think I would prefer the second option too, looks "cleaner".

yay! ^.^

Why does the use have to declare the enum class Text though, it can't be in the library ?

Take another look: it defines the aliases that you give your text snippets for access. So it is essential to have that declared by the user so that they can do own text at all

But I'm thinking this syntax is pretty complicated for newbies, like, really (3 layers of { }, objects :: and all). So we consider newbies won't do multi-lang support anyway then ? Or would it possible to have the same structure, but with a simplified user-side usage ? Using only syntax and symbols they already know ?

First off - I thought that if we provided them an example they would just copypaste the block and adjust it to their needs. That is at least what i did when learning programming
Second off - we could define variables for Lang_Code::en being LANG_CODE_EN etc., no problem at all.
Third off - about the Text:: enum, we could recommend enum but allow users to #define their own ints so that everything gobbels up ints, too.

EDIT: with the #defines and stuff the example would boil down to

#define TEXT_WELCOME 0
#define TEXT_SOMETHING_ELSE 1
#define TEXT_THE_GAME 2

const Lang_Entry asdf[] = {
	{ TEXT_WELCOME, {
		{ LANG_CODE_EN, "Welcome!" },
		{ LANG_CODE_DE, "Wilkommen!" },
	}},
	{ TEXT_SOMETHING_ELSE, {
		{ LANG_CODE_EN, "Something else!" },
		{ LANG_CODE_DE, "Etwas anderes!" },
	}},
	{ TEXT_THE_GAME, {
		{ LANG_CODE_EN, "The Game!" },
		{ LANG_CODE_DE, "Das Spiel!" },
	}},
};

I know it's crap and the syntax is wrong, but just to show you the idea:

I initially liked this idea as it solves the naming problem......however than i realized its big flaw: If you have the text stuff defined in a seperate file, you will have to do "extern const myText;" in another file to be able to access that variable. With many text things it would be a huge hassle and very verbose...

Until we decide we can already make the global language setting available and let people free to do their own implementation. A language API isn't the priority _at all_ right now

Besides doing the font the language api would be extremely little work....so I guess I'll work on the font today. Since there were no objections to the charset i'll tailor it for that ^.^

Sorunome

NEW 2 months ago

Drakker Drakker

Users writing messy code is their own problem, not your's. ;)      

It will be mine once they ask for help on the forums....

Can you do a quick speed test and see if there's any difference?

It would be highly dependant on how many different text things you defined

Sorunome

NEW 2 months ago

Rodot Rodot

I think I would prefer the second option too, looks "cleaner".

yay! ^.^

Why does the use have to declare the enum class Text though, it can't be in the library ?

Take another look: it defines the aliases that you give your text snippets for access. So it is essential to have that declared by the user so that they can do own text at all

But I'm thinking this syntax is pretty complicated for newbies, like, really (3 layers of { }, objects :: and all). So we consider newbies won't do multi-lang support anyway then ? Or would it possible to have the same structure, but with a simplified user-side usage ? Using only syntax and symbols they already know ?

First off - I thought that if we provided them an example they would just copypaste the block and adjust it to their needs. That is at least what i did when learning programming
Second off - we could define variables for Lang_Code::en being LANG_CODE_EN etc., no problem at all.
Third off - about the Text:: enum, we could recommend enum but allow users to #define their own ints so that everything gobbels up ints, too.

EDIT: with the #defines and stuff the example would boil down to

#define TEXT_WELCOME 0
#define TEXT_SOMETHING_ELSE 1
#define TEXT_THE_GAME 2

const Lang_Entry asdf[] = {
	{ TEXT_WELCOME, {
		{ LANG_CODE_EN, "Welcome!" },
		{ LANG_CODE_DE, "Wilkommen!" },
	}},
	{ TEXT_SOMETHING_ELSE, {
		{ LANG_CODE_EN, "Something else!" },
		{ LANG_CODE_DE, "Etwas anderes!" },
	}},
	{ TEXT_THE_GAME, {
		{ LANG_CODE_EN, "The Game!" },
		{ LANG_CODE_DE, "Das Spiel!" },
	}},
};

I know it's crap and the syntax is wrong, but just to show you the idea:

I initially liked this idea as it solves the naming problem......however than i realized its big flaw: If you have the text stuff defined in a seperate file, you will have to do "extern const myText;" in another file to be able to access that variable. With many text things it would be a huge hassle and very verbose...

Until we decide we can already make the global language setting available and let people free to do their own implementation. A language API isn't the priority _at all_ right now

Besides doing the font the language api would be extremely little work....so I guess I'll work on the font today. Since there were no objections to the charset i'll tailor it for that ^.^

Rodot

NEW 2 months ago

The thing with my solution is that it doesnt work as is. You can't call myText.get() as it's an array, not an object. But you could call gb.language.get(myText), which looks OK IMHO.

you will have to do "extern const myText;" in another file to be able to access that variable. With many text things it would be a huge hassle and very verbose...

The "extern" thing isn't an issue as you will also have to use it for all your assets (eg. image .h files). Or you could just put all your text on top of your program, where you declare all your global variables, "the arduino way" haha.

Sorunome

2 months ago

The thing with my solution is that it doesnt work as is. You can't call myText.get() as it's an array, not an object. But you could call gb.language.get(myText), which looks OK IMHO.

Easily fixable by adjusting your thing ever so slightly, to look like this:

const MultiLang myText = {{
  {LANG_EN, "Hello"},
  {LANG_FR, "Bonjour"},
}};

const MultiLang myOtherText = {{
  {LANG_EN, "High scores"},
  {LANG_FR, "Meilleurs scores"},
}};

gb.display.print(myText.get());
gb.display.print(myText.get(LANG_FR));

The "extern" thing isn't an issue as you will also have to use it for all your assets (eg. image .h files). Or you could just put all your text on top of your program, where you declare all your global variables, "the arduino way" haha.

Right, we could recommend putting all those defines into a .h file and just include it on the top of your ino or something. That could work. That would make your idea currently the best one, IMO

Thoughts of other people on this? Drakker? Myndale?

EDIT:

recommend would be putting the text definitions <somewhere> (.cpp, .ino, whatever the user wants) and having a .h file with all the "extern const MultiLang mytext;" etc.? Come to think of it, this way you will need to define the text name in two different locations, once for the actual object and once for the "extern blahblahblah" *thinking*

Sorunome

NEW 2 months ago

Rodot Rodot

The thing with my solution is that it doesnt work as is. You can't call myText.get() as it's an array, not an object. But you could call gb.language.get(myText), which looks OK IMHO.

Easily fixable by adjusting your thing ever so slightly, to look like this:

const MultiLang myText = {{
  {LANG_EN, "Hello"},
  {LANG_FR, "Bonjour"},
}};

const MultiLang myOtherText = {{
  {LANG_EN, "High scores"},
  {LANG_FR, "Meilleurs scores"},
}};

gb.display.print(myText.get());
gb.display.print(myText.get(LANG_FR));

The "extern" thing isn't an issue as you will also have to use it for all your assets (eg. image .h files). Or you could just put all your text on top of your program, where you declare all your global variables, "the arduino way" haha.

Right, we could recommend putting all those defines into a .h file and just include it on the top of your ino or something. That could work. That would make your idea currently the best one, IMO

Thoughts of other people on this? Drakker? Myndale?

EDIT:

recommend would be putting the text definitions <somewhere> (.cpp, .ino, whatever the user wants) and having a .h file with all the "extern const MultiLang mytext;" etc.? Come to think of it, this way you will need to define the text name in two different locations, once for the actual object and once for the "extern blahblahblah" *thinking*

Rodot

2 months ago

Okay, but I'm not sure the {{ }} this is really friendly though, so gb.language.get(myText) might be better ?

Rodot

NEW 2 months ago

Sorunome Sorunome

Okay, but I'm not sure the {{ }} this is really friendly though, so gb.language.get(myText) might be better ?

Sorunome

2 months ago

Okay, but I'm not sure the {{ }} this is really friendly though, so gb.language.get(myText) might be better ?

That would work, too, i am fine either way, like, i literetally have no preference on that. So it is completally up to you guys!


Sorunome

NEW 2 months ago

Rodot Rodot

Okay, but I'm not sure the {{ }} this is really friendly though, so gb.language.get(myText) might be better ?

That would work, too, i am fine either way, like, i literetally have no preference on that. So it is completally up to you guys!


Sorunome

NEW 2 months ago

Rodot

2 months ago

Woo great example, limpid, and the right level of verbose. Kudos ! :D

Edit : And of course, great API too ^^

Rodot

NEW 2 months ago

Sorunome Sorunome

Woo great example, limpid, and the right level of verbose. Kudos ! :D

Edit : And of course, great API too ^^

You must be logged in in order to post a message on the forum

Log in