I’m developing an application that uses TTS for Android.
I don’t seem to get something in setting the language for TTS.
I have been able to set my application to speak in Italian, but not in Finnish or Russian. However, both those are shown when I output this:
Log.i("-------------",Arrays.toString(Locale.getAvailableLocales()));
Which gives:
[ar, ar_EG, bg, bg_BG, ca, ca_ES, cs, cs_CZ, da, da_DK, de, de_AT, de_BE, de_CH, de_DE, de_LI, de_LU, el, el_CY, el_GR, en, en_AU, en_BE, en_BW, en_BZ, en_CA, en_GB, en_HK, en_IE, en_IN, en_JM, en_MH, en_MT, en_NA, en_NZ, en_PH, en_PK, en_RH, en_SG, en_TT, en_US, en_US_POSIX, en_VI, en_ZA, en_ZW, es, es_AR, es_BO, es_CL, es_CO, es_CR, es_DO, es_EC, es_ES, es_GT, es_HN, es_MX, es_NI, es_PA, es_PE, es_PR, es_PY, es_SV, es_US, es_UY, es_VE, et, et_EE, eu, eu_ES, fa, fa_IR, **fi, fi_FI**, fr, fr_BE, fr_CA, fr_CH, fr_FR, fr_LU, fr_MC, gl, gl_ES, hr, hr_HR, hu, hu_HU, in, in_ID, is, is_IS, it, it_CH, it_IT, iw, iw_IL, ja, ja_JP, kk, kk_KZ, ko, ko_KR, lt, lt_LT, lv, lv_LV, mk, mk_MK, ms, ms_BN, ms_MY, nl, nl_BE, nl_NL, no, no_NO, no_NO_NY, pl, pl_PL, pt, pt_BR, pt_PT, ro, ro_RO, **ru, ru_RU**, ru_UA, sh, sh_BA, sh_CS, sh_YU, sk, sk_SK, sl, sl_SI, sq, sq_AL, sr, sr_BA, sr_ME, sr_RS, sv, sv_FI, sv_SE, th, th_TH, tr, tr_TR, uk, uk_UA, vi, vi_VN, zh, zh_CN, zh_HK, zh_HANS_SG, zh_HANT_MO, zh_MO, zh_TW]
So, what’s the problem?
Locale.getAvailableLocales() returns the locale codes understood by the device, which has no relation to the locales supported by the current text-to-speech engine.
After creating a connection to a TTS engine, you can call TextToSpeech.isLanguageAvailable(Locale) to determine if a language is supported by the engine and whether data for that particular language is installed.
Related
I need to return the short version of the weekdays names using getShortWeekdays() in Android. I tested with different device language settings. Although it seems to be working with various language settings, unfortunately when the selected language is Portuguese (Portugal), it returns the full day names. Any advice?
DateFormatSymbols symbols = new DateFormatSymbols(Locale.getDefault());
String[] dayNames = symbols.getShortWeekdays();
Output if language is English (United States):
["", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
Output if language is Portuguese (Brasil):
["", "dom.", "seg.", "ter.", "qua.", "qui.", "sex.", "sáb."]
Output if language is Portuguese (Portugal):
["", "domingo", "segunda", "terça", "quarta", "quinta", "sexta", "sábado"]
At first, I wondered if using a different package for the method's class would produce different results, but it didn't.
final java.util.Locale pt_BR = new java.util.Locale("pt", "BR");
android.icu.text.DateFormatSymbols symbolsPtBr1 = new android.icu.text.DateFormatSymbols(pt_BR);
String[] dayNamesBtBr1 = symbolsPtBr1.getShortWeekdays();
Log.e("LOCALE_ANDROID", String.format("%s(%s): (android.icu.text): %s", pt_BR.getDisplayVariant(), pt_BR.getDisplayName(), Arrays.toString(dayNamesBtBr1)));
java.text.DateFormatSymbols symbolsPtBr2 = new java.text.DateFormatSymbols(pt_BR);
String[] dayNamesBtBr2 = symbolsPtBr2.getShortWeekdays();
Log.e("LOCALE_ANDROID", String.format("%s(%s): (java.text): %s", pt_BR.getDisplayVariant(), pt_BR.getDisplayName(), Arrays.toString(dayNamesBtBr2)));
final java.util.Locale pt_PT = new java.util.Locale("pt", "PT");
android.icu.text.DateFormatSymbols symbolsPtPt1 = new android.icu.text.DateFormatSymbols(pt_PT);
String[] dayNamesBtPt1 = symbolsPtPt1.getShortWeekdays();
Log.e("LOCALE_ANDROID", String.format("%s(%s): (android.icu.text): %s", pt_PT.getDisplayVariant(), pt_PT.getDisplayName(), Arrays.toString(dayNamesBtPt1)));
java.text.DateFormatSymbols symbolsPtPt2 = new java.text.DateFormatSymbols(pt_PT);
String[] dayNamesBtPt2 = symbolsPtPt2.getShortWeekdays();
Log.e("LOCALE_ANDROID", String.format("%s(%s): (java.text): %s", pt_PT.getDisplayVariant(), pt_PT.getDisplayName(), Arrays.toString(dayNamesBtPt2)));
The log output (tested on Android 7.0):
LOCALE_ANDROID: (Portuguese (Brazil)): (android.icu.text): [, Dom, Seg, Ter, Qua, Qui, Sex, Sáb]
LOCALE_ANDROID: (Portuguese (Brazil)): (java.text): [, Dom, Seg, Ter, Qua, Qui, Sex, Sáb]
LOCALE_ANDROID: (Portuguese (Portugal)): (android.icu.text): [, domingo, segunda, terça, quarta, quinta, sexta, sábado]
LOCALE_ANDROID: (Portuguese (Portugal)): (java.text): [, domingo, segunda, terça, quarta, quinta, sexta, sábado]
Therefore, you can just truncate each weekday to a two- or three-letter symbol length with substring(0, len) and capitalize it with StringUtils.capitalize() method as this is the approach used for the issue in OsmAnd https://github.com/osmandapp/Osmand/issues/5115
So I have ViewModel with a method to get data using context. But in testing the data is null because possibly there is no context when testing.
The code is as follow:
This is testing ViewModel code
public class FilmViewTest {
private FilmView filmView;
#Before
public void setUp() {
Application context = Mockito.mock(Application.class);
filmView = new FilmView(context);
FilmData filmData = new FilmData(context);
}
// ubah poster menjadi link
#Test
public void getFilms() {
List<ResultsItem> films = filmView.getFilms();
assertNotNull(films);
assertEquals(10, films.size());
}
}
and here is the ViewModel itself
public class FilmView extends AndroidViewModel {
public FilmView(#NonNull Application application) {
super(application);
}
private Context context = getApplication().getApplicationContext();
private FilmData filmData = new FilmData(context);
public List<ResultsItem> getFilms() {
return FilmData.generateFilms();
}
}
the data class
public class FilmData {
private static Context context;
public FilmData(Context context) {
FilmData.context = context;
}
public static ArrayList<ResultsItem> generateFilms() {
ArrayList<ResultsItem> courses = new ArrayList<>();
courses.add(new ResultsItem(
"A Star is Born",
"Seasoned musician Jackson Maine discovers — " +
"and falls in love with — struggling artist Ally." +
" She has just about given up on her dream to make it big as a singer — until Jack coaxes her into the spotlight. But even as Ally's career takes off, the personal " +
"side of their relationship is breaking down, as Jack fights an ongoing battle with his own internal demons.",
context.getResources().getDrawable(R.drawable.poster_a_start_is_born),
346, " February 19, 2019", 75, 50));
courses.add(new ResultsItem("Aquaman",
"Once home to the most advanced civilization on Earth, Atlantis is now an underwater kingdom ruled by the power-hungry King Orm. With a vast army at his disposal, Orm plans to conquer the remaining oceanic people and then the surface world. " +
"Standing in his way is Arthur Curry, Orm's half-human, " +
"half-Atlantean brother and true heir to the throne.",
context.getResources().getDrawable(R.drawable.poster_aquaman),
238, "December 21, 2018", 68, 60));
courses.add(new ResultsItem("Bohemian Rhapsody ",
"Singer Freddie Mercury, guitarist Brian May, drummer Roger Taylor and bass guitarist John Deacon take the music world by storm when they form the rock 'n' roll band Queen in 1970. Hit songs become instant classics. When Mercury's increasingly wild lifestyle starts to spiral out of control, " +
"Queen soon faces its greatest challenge yet – " +
"finding a way to keep the band together amid the success and excess.",
context.getResources().getDrawable(R.drawable.poster_bohemian),
892, "November 2, 2018", 80, 299));
courses.add(new ResultsItem("Alita: Battle Angel ",
"When Alita awakens with no memory of who she is in a future world she does not recognize, she is taken in by Ido, a compassionate doctor who realizes that" +
" somewhere in this abandoned cyborg " +
"shell is the heart and soul of a young woman with an extraordinary past.\n",
context.getResources().getDrawable(R.drawable.poster_alita),
289, "January 31, 2019", 69, 15 ));
courses.add(new ResultsItem(
"Cold Pursuit",
"The quiet family life of Nels Coxman, a snowplow driver, is upended after his son's murder. Nels begins a vengeful hunt for Viking, the drug lord he holds responsible for the killing, eliminating Viking's associates one by one. As Nels draws closer to Viking, his actions bring even more unexpected and violent consequences, as he proves that revenge is all in the execution.",
context.getResources().getDrawable(R.drawable.poster_cold_persuit),
54, "February 7, 2019", 54, 219));
courses.add(new ResultsItem(
"Creed",
"The former World Heavyweight Champion Rocky Balboa serves as a trainer and mentor to Adonis Johnson, the son of his late friend and former rival Apollo Creed.",
context.getResources().getDrawable(R.drawable.poster_creed),
129, "November 25, 2015", 73, 281));
courses.add(new ResultsItem("Fantastic Beasts: The Crimes of Grindelwald",
"Gellert Grindelwald has escaped imprisonment and has begun gathering followers to his cause—elevating wizards above all non-magical beings. The only one capable of putting a stop to him is the wizard he once called his closest friend, Albus Dumbledore. However, Dumbledore will need to seek help from the wizard who had thwarted Grindelwald once before, his former student Newt Scamander, who agrees to help, unaware of the dangers that lie ahead. Lines are drawn as love and loyalty are tested, even among the truest friends and family, in an increasingly divided wizarding world.",
context.getResources().getDrawable(R.drawable.poster_crimes),
123, "November 14, 2018", 69, 210));
courses.add(new ResultsItem("Glass ",
"In a series of escalating encounters, former security guard David Dunn uses his supernatural abilities to track Kevin Wendell Crumb, a disturbed man who has twenty-four personalities. Meanwhile, the shadowy presence of Elijah Price emerges as an orchestrator who holds secrets critical to both men.",
context.getResources().getDrawable(R.drawable.poster_glass).getCurrent(),
213, "January 16, 2019", 66, 190));
courses.add(new ResultsItem("How to Train Your Dragon ",
"As the son of a Viking leader on the cusp of manhood, shy Hiccup Horrendous Haddock III faces a rite of passage: he must kill a dragon to prove his warrior mettle. But after downing a feared dragon, he realizes that he no longer wants to destroy it, and instead befriends the beast – which he names Toothless – much to the chagrin of his warrior father",
context.getResources().getDrawable(R.drawable.poster_how_to_train),
319, "March 10, 2010", 77, 219));
courses.add(new ResultsItem(
"Avengers: Infinity War",
"As the Avengers and their allies have continued to protect the world from threats too large for any one hero to handle, a new danger has emerged from the cosmic shadows: Thanos. A despot of intergalactic infamy, his goal is to collect all six Infinity Stones, artifacts of unimaginable power, and use them to inflict his twisted will on all of reality. Everything the Avengers have fought for has led up to this moment - the fate of Earth and existence itself has never been more uncertain.",
context.getResources().getDrawable(R.drawable.poster_infinity_war),
193, "April 25, 2018", 83, 21933));
return courses;
}
}
it gives null error on the methods
java.lang.NullPointerException
at com.example.movietv.film.utils.FilmData.generateFilms(FilmData.java:28)
at com.example.movietv.film.adapter.FilmView.getFilms(FilmView.java:26)
at com.example.movietv.film.adapter.FilmViewTest.getFilms(FilmViewTest.java:32)
Any help would be appreciated! Thanks!
Ideally, you should not be passing entire drawable object into model context.getResources().getDrawable(R.drawable.poster_infinity_war). Instead, just pass the int of the resource as
new ResultsItem("How to Train Your Dragon ",
"As ... father", R.drawable.poster_how_to_train,
319, "March 10, 2010", 77, 219)
Note: You need to change ResultsItem Model accordingly
and then, when you are actually rendering it in an Adapter:
public class MyOrdersAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<MyOrder> restaurantList;
private Context context;
public MyOrdersAdapter(Context context, List<MyOrder> restaurantList) {
this.context = context;
this.restaurantList = restaurantList;
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, int position) {
if(context != null)
holder.data.setBackgroundResource(context.getResources().getDrawable(R.drawable.poster_infinity_war));
}
...
}
This will help you prevent OutOfMemoryException in future
I'm writing an app in which I want to display a string, describing an object in my model, to the user. I've started thinking about localization and I'm a bit stumped as to the best approach to deal with the difference in grammar between languages.
Imagine if in my model I have a verb an object and a quantity (pseudocode):
int _quantity = 6;
String _object = "#string/object";
String _verb = "#string/verb";
This should render in English as _verb + _quantity + _object (i.e. "eat 6 eggs", whereas in German it should render as _quantity + _object + _verb (i.e. "6 eggs eat" (but, obviously, using German word tokens rather than English :) ))
Is there a standard approach to this type of problem in Android?
Thanks.
OK, I've come up with a solution which, based on what I know, is the best I can find. If anyone has a better solution, please post!
1) In strings.xml, define any bits of text needed (for me, these equated to the localised names for enumeration members and for the string used to build natural language strings (more on this in a bit))
Example:
<resources>
...
<string name="verb_ate">ate</string>
<string name="verb_threw">threw</string>
...
<string name="modelobject_naturallanguagedescription">I {0} {1} {2}</string>
...
</resources>
2) In any enumerations, decorate them with the resource ids of the strings they are to use for display
Example:
public enum VerbType
Ate(R.string.verb_ate),
Threw(R.string.verb_threw);
private final int _stringID;
private VerbType(int stringID) {
_stringID = stringID;
}
public int getStringID() {
return _stringID;
}
}
3) Make a helper function which will take the model object and the context and will piece together the required natural language string (using java.text.MessageFormat), plus a helper function for each enumeration:
Example (assume that ModelObject has _verb, _quantity, _object and relevant getters):
public static String getVerb(VerbType v, Context c) {
return c.getResources().getString(v.getStringID());
}
public static String getNaturalLanguageString(ModelObject o, Context c) {
MessageFormat mf = new MessageFormat(c.getResources().getString(R.string.modelobject_naturallanguagedescription);
return mf.format(new Object[] {getVerb(o.getVerb(),c), o.getQuantity(), o.getObject()});
}
So, having done all of this, it's easy enough to define another strings file for a different language (German for example (I don't know German, so the language won't be correct)):
<resources>
...
<string name="verb_ate">gegessen</string>
<string name="verb_threw">gethrown</string>
...
<string name="modelobject_naturallanguagedescription">Ich haben {1} {2} {0}</string>
...
</resources>
So, in an english locale getNaturalLanguageString might return:
I ate 6 eggs
whereas in a German locale it might return:
Ich haben 6 eggs gegessen
Can anybody tell me what is the value we have write for spanish in android. For example we write the value for English is 'en' Chinese 'zh'. Then what is the value for Spanish...???
Thanx in Advance...!!!
Thanx to All,
Now I found Answer. That value is only 'es'. And I follow this documents.
Its a,
es - Spanish
es_US - Spanish, US
Look at this link,
Android Supported Language and Locales
and
http://download1.parallels.com/SiteBuilder/Windows/docs/3.2/en_US/sitebulder-3.2-win-sdk-localization-pack-creation-guide/30801.htm
Its Spanish (es_ES) . `Its supported from android version 1.5`
Look at this link might help you a lot .
You can find all the available Locale of the device using the following function.
Locale loc = new Locale("en");
Log.i("-------------",Arrays.toString(loc.getAvailableLocales()));
Output
I/-------------( 4390): [ar, ar_EG, bg, bg_BG, ca, ca_ES, cs, cs_CZ, da, da_DK, de, de_AT, de_BE, de_CH, de_DE, de_LI, de_LU, el, el_CY, el_GR, en, en
_AU, en_BE, en_BW, en_BZ, en_CA, en_GB, en_HK, en_IE, en_IN, en_JM, en_MH, en_MT, en_NA, en_NZ, en_PH, en_PK, en_RH, en_SG, en_TT, en_US, en_US_POSIX,
en_VI, en_ZA, en_ZW, es, es_AR, es_BO, es_CL, es_CO, es_CR, es_DO, es_EC, es_ES, es_GT, es_HN, es_MX, es_NI, es_PA, es_PE, es_PR, es_PY, es_SV, es_US
, es_UY, es_VE, et, et_EE, eu, eu_ES, fa, fa_IR, fi, fi_FI, fr, fr_BE, fr_CA, fr_CH, fr_FR, fr_LU, fr_MC, gl, gl_ES, hr, hr_HR, hu, hu_HU, in, in_ID,
is, is_IS, it, it_CH, it_IT, iw, iw_IL, ja, ja_JP, kk, kk_KZ, ko, ko_KR, lt, lt_LT, lv, lv_LV, mk, mk_MK, ms, ms_BN, ms_MY, nl, nl_BE, nl_NL, no, no_N
O, no_NO_NY, pl, pl_PL, pt, pt_BR, pt_PT, ro, ro_RO, ru, ru_RU, ru_UA, sh, sh_BA, sh_CS, sh_YU, sk, sk_SK, sl, sl_SI, sq, sq_AL, sr, sr_BA, sr_ME, sr_
RS, sv, sv_FI, sv_SE, th, th_TH, tr, tr_TR, uk, uk_UA, vi, vi_VN, zh, zh_CN, zh_HK, zh_HANS_SG, zh_HANT_MO, zh_MO, zh_TW]
I'm working on a text-to-speech implementation of a flashcard program. Text in different languages should be read out. In order to do this properly the user has to select the language of the text to read (will be stored and used later without question).
Is there a possibility of getting the available TTS languages on an Android system? If not, is there a possibility of getting all availably locales on the system?
I guess, I got it: getAvailableLocales() and tts.isLocaleAvailable(locale)
Someone else has done the hard work, at http://kaviddiss.com/2012/08/12/android-text-to-speech-languages/
To save you time, here's their code extract
TextToSpeech tts = ...
// let's assume tts is already inited at this point:
Locale[] locales = Locale.getAvailableLocales();
List<Locale> localeList = new ArrayList<Locale>();
for (Locale locale : locales) {
int res = tts.isLanguageAvailable(locale);
if (res == TextToSpeech.LANG_COUNTRY_AVAILABLE) {
localeList.add(locale);
}
}
// at this point the localeList object will contain
// all available languages for Text to Speech
The results depend on which TTS engine has been selected. For instance, one of my phones includes both the Pico-TTS and Google-text-to-speech engines.
Q-Smart (Vietnamese Phone with Google TTS as selected engine)
D/SpeakRepeatedly( 3979): Engine Google Text-to-speech Engine:com.google.android.tts
D/SpeakRepeatedly( 3979): Engine Pico TTS:com.svox.pico
D/SpeakRepeatedly( 3979): German (Germany):German:de_DE
D/SpeakRepeatedly( 3979): English (United Kingdom):English:en_GB
D/SpeakRepeatedly( 3979): English (United States):English:en_US
D/SpeakRepeatedly( 3979): English (United States,Computer):English:en_US_POSIX
D/SpeakRepeatedly( 3979): Spanish (Spain):Spanish:es_ES
D/SpeakRepeatedly( 3979): French (France):French:fr_FR
D/SpeakRepeatedly( 3979): Italian (Italy):Italian:it_IT
D/SpeakRepeatedly( 3979): Portuguese (Brazil):Portuguese:pt_BR
D/SpeakRepeatedly( 3979): Portuguese (Portugal):Portuguese:pt_PT
And with Pico selected
D/SpeakRepeatedly( 4837): Engine Google Text-to-speech Engine:com.google.android.tts
D/SpeakRepeatedly( 4837): Engine Pico TTS:com.svox.pico
D/SpeakRepeatedly( 4837): German (Germany):German:de_DE
D/SpeakRepeatedly( 4837): English (United Kingdom):English:en_GB
D/SpeakRepeatedly( 4837): English (United States):English:en_US
D/SpeakRepeatedly( 4837): English (United States,Computer):English:en_US_POSIX
D/SpeakRepeatedly( 4837): Spanish (Spain):Spanish:es_ES
D/SpeakRepeatedly( 4837): French (France):French:fr_FR
D/SpeakRepeatedly( 4837): Italian (Italy):Italian:it_IT
Note:
Portuguese isn’t listed in the TTS Settings UI. When I select Portuguese programmatically in my app it speaks with a Portuguese accent! FWIW here's my code to select Portuguese (it accepts both Brazilian and Portuguese locales).
if (locale.getDisplayName().startsWith("Portuguese")) {
Log.i(SPEAK_REPEATEDLY, "Setting Locale to: " + locale.toString());
tts.setLanguage(locale);
}
}
Since different TTS engines return different results for isLanguageAvailable, I found that the following solution works best on several common TTS engines.
Please also note that starting with Android Lollipop, there is a simple method in TextToSpeech called getAvailableLanguages that does that easily for you (if the device is running API 21 or later).
You need to call the following methods in the onInit method of your OnInitListener assigned to the TextToSpeech object.
ArrayList<Locale> languages;
TextToSpeech initTTS;
private void initSupportedLanguagesLollipop()
{
Set<Locale> availableLocales = initTTS.getAvailableLanguages();
for (Locale locale : availableLocales)
{
languages.add(locale);
}
}
private void initSupportedLanguagesLegacy()
{
Locale[] allLocales = Locale.getAvailableLocales();
for (Locale locale : allLocales)
{
try
{
int res = initTTS.isLanguageAvailable(locale);
boolean hasVariant = (null != locale.getVariant() && locale.getVariant().length() > 0);
boolean hasCountry = (null != locale.getCountry() && locale.getCountry().length() > 0);
boolean isLocaleSupported =
false == hasVariant && false == hasCountry && res == TextToSpeech.LANG_AVAILABLE ||
false == hasVariant && true == hasCountry && res == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
res == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
Log.d(TAG, "TextToSpeech Engine isLanguageAvailable " + locale + " (supported=" + isLocaleSupported + ",res=" + res + ", country=" + locale.getCountry() + ", variant=" + locale.getVariant() + ")");
if (true == isLocaleSupported)
{
languages.add(locale);
}
}
catch (Exception ex)
{
Log.e(TAG, "Error checking if language is available for TTS (locale=" + locale +"): " + ex.getClass().getSimpleName() + "-" + ex.getMessage());
}
}
}
Find all available TTS Locale on the device using following function.
Locale.getAvailableLocales()
Output of: Arrays.toString(Locale.getAvailableLocales())
[ar, ar_EG, bg, bg_BG, ca, ca_ES, cs, cs_CZ, da, da_DK, de, de_AT, de_BE, de_CH, de_DE, de_LI, de_LU, el, el_CY, el_GR, en, en
_AU, en_BE, en_BW, en_BZ, en_CA, en_GB, en_HK, en_IE, en_IN, en_JM, en_MH, en_MT, en_NA, en_NZ, en_PH, en_PK, en_RH, en_SG, en_TT, en_US, en_US_POSIX,
en_VI, en_ZA, en_ZW, es, es_AR, es_BO, es_CL, es_CO, es_CR, es_DO, es_EC, es_ES, es_GT, es_HN, es_MX, es_NI, es_PA, es_PE, es_PR, es_PY, es_SV, es_US
, es_UY, es_VE, et, et_EE, eu, eu_ES, fa, fa_IR, fi, fi_FI, fr, fr_BE, fr_CA, fr_CH, fr_FR, fr_LU, fr_MC, gl, gl_ES, hr, hr_HR, hu, hu_HU, in, in_ID,
is, is_IS, it, it_CH, it_IT, iw, iw_IL, ja, ja_JP, kk, kk_KZ, ko, ko_KR, lt, lt_LT, lv, lv_LV, mk, mk_MK, ms, ms_BN, ms_MY, nl, nl_BE, nl_NL, no, no_N
O, no_NO_NY, pl, pl_PL, pt, pt_BR, pt_PT, ro, ro_RO, ru, ru_RU, ru_UA, sh, sh_BA, sh_CS, sh_YU, sk, sk_SK, sl, sl_SI, sq, sq_AL, sr, sr_BA, sr_ME, sr_
RS, sv, sv_FI, sv_SE, th, th_TH, tr, tr_TR, uk, uk_UA, vi, vi_VN, zh, zh_CN, zh_HK, zh_HANS_SG, zh_HANT_MO, zh_MO, zh_TW]
Starting from Android 5.0 (API level 21), TextToSpeech.getAvailableLanguages has been added to fetch a set of all locales supported by the TTS engine.
TextToSpeech tts; // assume this is initialized
tts.getAvailableLanguages(); // returns a set of available locales
I have also noticed that the set of locales returned by TextToSpeech.getAvailableLanguages might not be a strict subset of Locale.getAvailableLocales, i.e. there might a locale supported by the TTS engine that isn't supported by the system.