-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
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
Support serving of backend translations #12453
Conversation
@@ -19,7 +19,8 @@ | |||
from homeassistant.components.http import HomeAssistantView | |||
from homeassistant.components.http.auth import is_trusted_ip | |||
from homeassistant.config import find_config_file, load_yaml_config_file | |||
from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED | |||
from homeassistant.const import ( |
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.
'homeassistant.const.EVENT_TRANSLATIONS_UPDATED' imported but unused
"""Return themes.""" | ||
hass = request.app['hass'] | ||
|
||
resources = hass.data[DATA_TRANSLATIONS].get(language) |
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 would prefer if we structure this like we just restructured service descriptions: on demand.
So have this view look at which components are loaded (hass.config.components
), look at their file names and see if they have files. For example, the light folder would look like:
__init__.py
services.yaml
strings.json
strings.nl.json
Bonus points if the frontend would be able to fetch the strings on a per component basis. So if it sees a string for component.light.setup.bla
, it will only fetch the light strings from the backend.
homeassistant/helpers/translation.py
Outdated
output = {} | ||
for key, value in data.items(): | ||
if isinstance(value, dict): | ||
output.update(recursive_flatten('{}{}.'.format(prefix, key), value)) |
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.
line too long (80 > 79 characters)
homeassistant/helpers/translation.py
Outdated
output = {} | ||
for key, value in data.items(): | ||
if isinstance(value, dict): | ||
output.update(recursive_flatten('{}{}.'.format(prefix, key), value)) |
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.
line too long (80 > 79 characters)
eb45f41
to
4084719
Compare
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.
We should either have a single file per component or break the world on duplicate keys.
|
||
@asyncio.coroutine | ||
def get(self, request, language): | ||
"""Return themes.""" |
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.
Update doc
homeassistant/helpers/translation.py
Outdated
platform = component.split('.', 1)[1] | ||
filename = 'strings.{}.json'.format(platform) | ||
else: | ||
filename = 'strings.json' |
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.
This won't work for components without their own dir
script/translations_upload_merge.py
Outdated
|
||
os.makedirs("build", exist_ok=True) | ||
|
||
json_util.save_json(os.path.join("build", "translations-upload.json"), translations) |
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.
line too long (88 > 79 characters)
|
||
|
||
|
||
def main(): |
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.
too many blank lines (3)
#!/usr/bin/env python3 | ||
"""Merge all translation sources into a single JSON file.""" | ||
import glob | ||
import itertools |
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.
'itertools' imported but unused
homeassistant/helpers/translation.py
Outdated
name = component | ||
|
||
|
||
module = get_component(component) |
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.
too many blank lines (2)
script/translations_upload_merge.py
Outdated
|
||
os.makedirs("build", exist_ok=True) | ||
|
||
json_util.save_json(os.path.join("build", "translations-upload.json"), translations) |
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.
line too long (88 > 79 characters)
|
||
|
||
|
||
def main(): |
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.
too many blank lines (3)
#!/usr/bin/env python3 | ||
"""Merge all translation sources into a single JSON file.""" | ||
import glob | ||
import itertools |
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.
'itertools' imported but unused
homeassistant/helpers/translation.py
Outdated
name = component | ||
|
||
|
||
module = get_component(component) |
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.
too many blank lines (2)
0caa343
to
83336a4
Compare
I think that we should only accept the format Edit: I misread. You used the word component but meant platform. For platforms we should indeed stick to |
(btw no access to temporary lokalise project) |
if domain not in resources: | ||
resources[domain] = {} | ||
|
||
# Add the translations for this component to the domain resources. |
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.
Something I was thinking about the other day, especially now that we're doing more entity based config: should we store platform as an attribute in the state machine?
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.
This one is going to get massive for sensor component.
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.
The platform as an attribute would certainly be a big help here. It would let us completely avoid this problem and keep the translations namespaced separately. That's something we could add later though if we decide to make it available, as the conflict only exists in the API response. All of our translations at rest are stored separately per platform.
And FWIW this will only return translations for loaded components, so although we'll have lots of translations merged, each instance should only serve the translations it's using.
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.
The platform as an attribute would certainly be a big help here. It would let us completely avoid this problem and keep the translations namespaced separately. That's something we could add later though if we decide to make it available, as the conflict only exists in the API response. All of our translations at rest are stored separately per platform.
And FWIW this will only return translations for loaded components, so although we'll have lots of translations merged, each instance should only serve the translations it's using.
homeassistant/helpers/translation.py
Outdated
components = hass.config.components | ||
|
||
# Load missing files | ||
missing = set() |
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.
FYI Sets can be subtracted from one another.
missing = hass.config.components - set(translation_cache)
homeassistant/helpers/translation.py
Outdated
load_translations_files, missing) | ||
|
||
# Update cache | ||
for component in components: |
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.
Are you calculating missing
again here?
homeassistant/helpers/translation.py
Outdated
# Update cache | ||
for component in components: | ||
if component not in translation_cache: | ||
json_file = component_translation_file(component, language) |
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 wonder if we can send a dictionary mapping component -> file to load_translation_files
so we don't have to generate all the keys again.
homeassistant/helpers/translation.py
Outdated
async def async_get_translations(hass, language): | ||
"""Return all backend translations.""" | ||
if language == 'en': | ||
resources = await async_get_component_resources(hass, language) |
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.
You can move this before the if
and turn the else
into an if language != 'en'
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.
It looks great. I realize I was a bit too picky with my comments, you can ignore them.
Get to take advantage of the slick Python 3.5 dict merging here.
No problem, it's all great advice per usual. I thought I made the Lokalise project public, but maybe it's because it's a trial. In any case, there's not much there. I mostly just linked it for an example of how the namespacing will look in Lokalise. I attached a screenshot to the PR description. I'm going to go ahead and get the docs ready and start setting up the live Lokalise project. It'll probably be a few days before I can get to it. |
OK, I've got the project spinned up on Lokalise. The last step should be to get the balloobbot credentials copied from the polymer build job to the backend master build job in Travis. After that we can push the button and see if it works. I've also put up a small PR to update the translation docs to include a link to the new project. home-assistant/home-assistant.io#4787 |
Added |
🤞 |
Description
This PR introduces the notion of backend-defined translations. When platforms are loaded, they will be able to register additional translation strings that can be served to clients. Fixes home-assistant/architecture#9
Still quite a ways from MVP, but I wanted to get some better visibility into our path forward and potentially some early feedback.
Source files
We'll manage our source files with git. Sources will be checked in adjacent to the component file. If the component is the main file for a Python package (i.e.
__init__.py
), the strings will be stored in a file namedstrings.json
. Otherwise, they'll be stored in a file namedstrings.<platform_name>.json
. Examples: (homeassistant/components/zwave/strings.json
,homeassistant/components/sensor/strings.season.json
)Lokalise downloads
The Lokalise downloads will also be checked in to the repo, but shouldn't ever be edited except through the
translation_download
script. They'll be stored in a hidden directory.translations
, also adjacent to the component. Similarly to the source files above, they'll be named with the format<language_code>.json
, or<component_name>.<language_code>.json
. Examples: (homeassistant/components/zwave/.translations/en.json
,homeassistant/components/sensor/.translations/season.de.json
)Frontend PR: home-assistant/frontend#894
Docs PR: home-assistant/home-assistant.io#4787
Lokalise temporary project: https://lokalise.co/project/104514435a91b786c77a78.62627628/
![image](https://user-images.githubusercontent.com/1386547/36701367-185730a8-1b21-11e8-8931-0ef5098f9f6a.png)
TODO:
src/translations/en.json
on the frontend.)Future PR:
EDIT: Migrated to home-assistant/frontend#498 for tracking
Developer docsAllow clients to fetch translations for a single componentNotify clients when component with translations is set upDetermine backend fetch caching and strategy (Possibly include etags with the response)