diff --git a/homeassistant/components/google_translate/const.py b/homeassistant/components/google_translate/const.py new file mode 100644 index 00000000000..e6361c1025e --- /dev/null +++ b/homeassistant/components/google_translate/const.py @@ -0,0 +1,306 @@ +"""Constant for google_translate integration.""" +from dataclasses import dataclass + +SUPPORT_LANGUAGES = [ + "af", + "ar", + "bg", + "bn", + "bs", + "ca", + "cs", + "cy", + "da", + "de", + "el", + "en", + "eo", + "es", + "et", + "fi", + "fr", + "gu", + "hi", + "hr", + "hu", + "hy", + "id", + "is", + "it", + "iw", + "ja", + "jw", + "km", + "kn", + "ko", + "la", + "lv", + "mk", + "ml", + "mr", + "my", + "ne", + "nl", + "no", + "pl", + "pt", + "ro", + "ru", + "si", + "sk", + "sq", + "sr", + "su", + "sv", + "sw", + "ta", + "te", + "th", + "tl", + "tr", + "uk", + "ur", + "vi", + # dialects + "zh-CN", + "zh-cn", + "zh-tw", + "en-us", + "en-ca", + "en-uk", + "en-gb", + "en-au", + "en-gh", + "en-in", + "en-ie", + "en-nz", + "en-ng", + "en-ph", + "en-za", + "en-tz", + "fr-ca", + "fr-fr", + "pt-br", + "pt-pt", + "es-es", + "es-us", +] + +SUPPORT_TLD = [ + "com", + "ad", + "ae", + "com.af", + "com.ag", + "com.ai", + "al", + "am", + "co.ao", + "com.ar", + "as", + "at", + "com.au", + "az", + "ba", + "com.bd", + "be", + "bf", + "bg", + "com.bh", + "bi", + "bj", + "com.bn", + "com.bo", + "com.br", + "bs", + "bt", + "co.bw", + "by", + "com.bz", + "ca", + "cd", + "cf", + "cg", + "ch", + "ci", + "co.ck", + "cl", + "cm", + "cn", + "com.co", + "co.cr", + "com.cu", + "cv", + "com.cy", + "cz", + "de", + "dj", + "dk", + "dm", + "com.do", + "dz", + "com.ec", + "ee", + "com.eg", + "es", + "com.et", + "fi", + "com.fj", + "fm", + "fr", + "ga", + "ge", + "gg", + "com.gh", + "com.gi", + "gl", + "gm", + "gr", + "com.gt", + "gy", + "com.hk", + "hn", + "hr", + "ht", + "hu", + "co.id", + "ie", + "co.il", + "im", + "co.in", + "iq", + "is", + "it", + "je", + "com.jm", + "jo", + "co.jp", + "co.ke", + "com.kh", + "ki", + "kg", + "co.kr", + "com.kw", + "kz", + "la", + "com.lb", + "li", + "lk", + "co.ls", + "lt", + "lu", + "lv", + "com.ly", + "co.ma", + "md", + "me", + "mg", + "mk", + "ml", + "com.mm", + "mn", + "ms", + "com.mt", + "mu", + "mv", + "mw", + "com.mx", + "com.my", + "co.mz", + "com.na", + "com.ng", + "com.ni", + "ne", + "nl", + "no", + "com.np", + "nr", + "nu", + "co.nz", + "com.om", + "com.pa", + "com.pe", + "com.pg", + "com.ph", + "com.pk", + "pl", + "pn", + "com.pr", + "ps", + "pt", + "com.py", + "com.qa", + "ro", + "ru", + "rw", + "com.sa", + "com.sb", + "sc", + "se", + "com.sg", + "sh", + "si", + "sk", + "com.sl", + "sn", + "so", + "sm", + "sr", + "st", + "com.sv", + "td", + "tg", + "co.th", + "com.tj", + "tl", + "tm", + "tn", + "to", + "com.tr", + "tt", + "com.tw", + "co.tz", + "com.ua", + "co.ug", + "co.uk", + "com.uy", + "co.uz", + "com.vc", + "co.ve", + "vg", + "co.vi", + "com.vn", + "vu", + "ws", + "rs", + "co.za", + "co.zm", + "co.zw", + "cat", +] + + +@dataclass +class Dialect: + """Language and TLD for a dialect supported by Google Translate.""" + + lang: str + tld: str + + +MAP_LANG_TLD: dict[str, Dialect] = { + "en-us": Dialect("en", "com"), + "en-gb": Dialect("en", "co.uk"), + "en-uk": Dialect("en", "co.uk"), + "en-au": Dialect("en", "com.au"), + "en-ca": Dialect("en", "ca"), + "en-in": Dialect("en", "co.in"), + "en-ie": Dialect("en", "ie"), + "en-za": Dialect("en", "co.za"), + "fr-ca": Dialect("fr", "ca"), + "fr-fr": Dialect("fr", "fr"), + "pt-br": Dialect("pt", "com.br"), + "pt-pt": Dialect("pt", "pt"), + "es-es": Dialect("es", "es"), + "es-us": Dialect("es", "com"), +} diff --git a/homeassistant/components/google_translate/tts.py b/homeassistant/components/google_translate/tts.py index eb6faf22bbc..c698a16baae 100644 --- a/homeassistant/components/google_translate/tts.py +++ b/homeassistant/components/google_translate/tts.py @@ -7,112 +7,41 @@ import voluptuous as vol from homeassistant.components.tts import CONF_LANG, PLATFORM_SCHEMA, Provider -_LOGGER = logging.getLogger(__name__) +from .const import MAP_LANG_TLD, SUPPORT_LANGUAGES, SUPPORT_TLD -SUPPORT_LANGUAGES = [ - "af", - "ar", - "bg", - "bn", - "bs", - "ca", - "cs", - "cy", - "da", - "de", - "el", - "en", - "eo", - "es", - "et", - "fi", - "fr", - "gu", - "hi", - "hr", - "hu", - "hy", - "id", - "is", - "it", - "iw", - "ja", - "jw", - "km", - "kn", - "ko", - "la", - "lv", - "mk", - "ml", - "mr", - "my", - "ne", - "nl", - "no", - "pl", - "pt", - "ro", - "ru", - "si", - "sk", - "sq", - "sr", - "su", - "sv", - "sw", - "ta", - "te", - "th", - "tl", - "tr", - "uk", - "ur", - "vi", - # dialects - "zh-CN", - "zh-cn", - "zh-tw", - "en-us", - "en-ca", - "en-uk", - "en-gb", - "en-au", - "en-gh", - "en-in", - "en-ie", - "en-nz", - "en-ng", - "en-ph", - "en-za", - "en-tz", - "fr-ca", - "fr-fr", - "pt-br", - "pt-pt", - "es-es", - "es-us", -] +_LOGGER = logging.getLogger(__name__) DEFAULT_LANG = "en" +SUPPORT_OPTIONS = ["tld"] + +DEFAULT_TLD = "com" + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES)} + { + vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES), + vol.Optional("tld", default=DEFAULT_TLD): vol.In(SUPPORT_TLD), + } ) async def async_get_engine(hass, config, discovery_info=None): """Set up Google speech component.""" - return GoogleProvider(hass, config[CONF_LANG]) + return GoogleProvider(hass, config[CONF_LANG], config["tld"]) class GoogleProvider(Provider): """The Google speech API provider.""" - def __init__(self, hass, lang): + def __init__(self, hass, lang, tld): """Init Google TTS service.""" self.hass = hass - self._lang = lang + if lang in MAP_LANG_TLD: + self._lang = MAP_LANG_TLD[lang].lang + self._tld = MAP_LANG_TLD[lang].tld + else: + self._lang = lang + self._tld = tld self.name = "Google" @property @@ -125,9 +54,20 @@ class GoogleProvider(Provider): """Return list of supported languages.""" return SUPPORT_LANGUAGES + @property + def supported_options(self): + """Return a list of supported options.""" + return SUPPORT_OPTIONS + def get_tts_audio(self, message, language, options=None): """Load TTS from google.""" - tts = gTTS(text=message, lang=language) + tld = self._tld + if language in MAP_LANG_TLD: + tld = MAP_LANG_TLD[language].tld + language = MAP_LANG_TLD[language].lang + if options is not None and "tld" in options.keys(): + tld = options["tld"] + tts = gTTS(text=message, lang=language, tld=tld) mp3_data = BytesIO() try: diff --git a/tests/components/google_translate/test_tts.py b/tests/components/google_translate/test_tts.py index 562f35655d0..67e840b1902 100644 --- a/tests/components/google_translate/test_tts.py +++ b/tests/components/google_translate/test_tts.py @@ -84,6 +84,7 @@ async def test_service_say(hass, mock_gtts, calls): assert mock_gtts.mock_calls[0][2] == { "text": "There is a person at the front door.", "lang": "en", + "tld": "com", } @@ -112,6 +113,7 @@ async def test_service_say_german_config(hass, mock_gtts, calls): assert mock_gtts.mock_calls[0][2] == { "text": "There is a person at the front door.", "lang": "de", + "tld": "com", } @@ -141,6 +143,96 @@ async def test_service_say_german_service(hass, mock_gtts, calls): assert mock_gtts.mock_calls[0][2] == { "text": "There is a person at the front door.", "lang": "de", + "tld": "com", + } + + +async def test_service_say_en_uk_config(hass, mock_gtts, calls): + """Test service call say with en-uk code in the config.""" + + await async_setup_component( + hass, + tts.DOMAIN, + {tts.DOMAIN: {"platform": "google_translate", "language": "en-uk"}}, + ) + + await hass.services.async_call( + tts.DOMAIN, + "google_translate_say", + { + "entity_id": "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + }, + blocking=True, + ) + + assert len(calls) == 1 + await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID]) + assert len(mock_gtts.mock_calls) == 2 + assert mock_gtts.mock_calls[0][2] == { + "text": "There is a person at the front door.", + "lang": "en", + "tld": "co.uk", + } + + +async def test_service_say_en_uk_service(hass, mock_gtts, calls): + """Test service call say with en-uk code in the config.""" + + await async_setup_component( + hass, + tts.DOMAIN, + {tts.DOMAIN: {"platform": "google_translate"}}, + ) + + await hass.services.async_call( + tts.DOMAIN, + "google_translate_say", + { + "entity_id": "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_LANGUAGE: "en-uk", + }, + blocking=True, + ) + + assert len(calls) == 1 + await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID]) + assert len(mock_gtts.mock_calls) == 2 + assert mock_gtts.mock_calls[0][2] == { + "text": "There is a person at the front door.", + "lang": "en", + "tld": "co.uk", + } + + +async def test_service_say_en_couk(hass, mock_gtts, calls): + """Test service call say in co.uk tld accent.""" + + await async_setup_component( + hass, tts.DOMAIN, {tts.DOMAIN: {"platform": "google_translate"}} + ) + + await hass.services.async_call( + tts.DOMAIN, + "google_translate_say", + { + "entity_id": "media_player.something", + tts.ATTR_MESSAGE: "There is a person at the front door.", + tts.ATTR_OPTIONS: {"tld": "co.uk"}, + }, + blocking=True, + ) + + assert len(calls) == 1 + url = await get_media_source_url(hass, calls[0].data[ATTR_MEDIA_CONTENT_ID]) + assert len(mock_gtts.mock_calls) == 2 + assert url.endswith(".mp3") + + assert mock_gtts.mock_calls[0][2] == { + "text": "There is a person at the front door.", + "lang": "en", + "tld": "co.uk", }