{"action":"created","comment":{"url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments/170490907","pull_request_review_id":99181522,"id":170490907,"diff_hunk":"@@ -0,0 +1,129 @@\n+\"\"\"Translation string lookup helpers.\"\"\"\n+import logging\n+# pylint: disable=unused-import\n+from typing import Optional  # NOQA\n+from os import path\n+\n+from homeassistant.loader import get_component, bind_hass\n+from homeassistant.util.json import load_json\n+\n+_LOGGER = logging.getLogger(__name__)\n+\n+TRANSLATION_STRING_CACHE = 'translation_string_cache'\n+\n+\n+def recursive_flatten(prefix, data):\n+    \"\"\"Return a flattened representation of dict data.\"\"\"\n+    output = {}\n+    for key, value in data.items():\n+        if isinstance(value, dict):\n+            output.update(\n+                recursive_flatten('{}{}.'.format(prefix, key), value))\n+        else:\n+            output['{}{}'.format(prefix, key)] = value\n+    return output\n+\n+\n+def flatten(data):\n+    \"\"\"Return a flattened representation of dict data.\"\"\"\n+    return recursive_flatten('', data)\n+\n+\n+def component_translation_file(component, language):\n+    \"\"\"Return the translation json file location for a component.\"\"\"\n+    if '.' in component:\n+        name = component.split('.', 1)[1]\n+    else:\n+        name = component\n+\n+    module = get_component(component)\n+    component_path = path.dirname(module.__file__)\n+\n+    # If loading translations for the package root, (__init__.py), the\n+    # prefix should be skipped.\n+    if module.__name__ == module.__package__:\n+        filename = '{}.json'.format(language)\n+    else:\n+        filename = '{}.{}.json'.format(name, language)\n+\n+    return path.join(component_path, '.translations', filename)\n+\n+\n+def load_translations_files(translation_files):\n+    \"\"\"Load and parse translation.json files.\"\"\"\n+    loaded = {}\n+    for translation_file in translation_files:\n+        loaded[translation_file] = load_json(translation_file)\n+\n+    return loaded\n+\n+\n+def build_resources(translation_cache, components):\n+    \"\"\"Build the resources response for the given components.\"\"\"\n+    # Build response\n+    resources = {}\n+    for component in components:\n+        if '.' not in component:\n+            domain = component\n+        else:\n+            domain = component.split('.', 1)[0]\n+\n+        if domain not in resources:\n+            resources[domain] = {}\n+\n+        # Add the translations for this component to the domain resources.\n+        # Since clients cannot determine which platform an entity belongs to,\n+        # all translations for a domain will be returned together.\n+        resources[domain].update(translation_cache[component])\n+\n+    return resources\n+\n+\n+@bind_hass\n+async def async_get_component_resources(hass, language):\n+    \"\"\"Return translation resources for all components.\"\"\"\n+    if TRANSLATION_STRING_CACHE not in hass.data:\n+        hass.data[TRANSLATION_STRING_CACHE] = {}\n+    if language not in hass.data[TRANSLATION_STRING_CACHE]:\n+        hass.data[TRANSLATION_STRING_CACHE][language] = {}\n+    translation_cache = hass.data[TRANSLATION_STRING_CACHE][language]\n+\n+    # Get the set of components\n+    components = hass.config.components\n+\n+    # Load missing files\n+    missing = set()\n+    for component in components:\n+        if component not in translation_cache:\n+            missing.add(component_translation_file(component, language))\n+\n+    if missing:\n+        loaded = await hass.async_add_job(\n+            load_translations_files, missing)\n+\n+    # Update cache\n+    for component in components:","path":"homeassistant/helpers/translation.py","position":105,"original_position":105,"commit_id":"9edf9ddaa31b927c32c1741feca912eb3f44450e","original_commit_id":"9edf9ddaa31b927c32c1741feca912eb3f44450e","user":{"login":"balloob","id":1444314,"avatar_url":"https://avatars3.githubusercontent.com/u/1444314?v=4","gravatar_id":"","url":"https://api.github.com/users/balloob","html_url":"https://github.com/balloob","followers_url":"https://api.github.com/users/balloob/followers","following_url":"https://api.github.com/users/balloob/following{/other_user}","gists_url":"https://api.github.com/users/balloob/gists{/gist_id}","starred_url":"https://api.github.com/users/balloob/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/balloob/subscriptions","organizations_url":"https://api.github.com/users/balloob/orgs","repos_url":"https://api.github.com/users/balloob/repos","events_url":"https://api.github.com/users/balloob/events{/privacy}","received_events_url":"https://api.github.com/users/balloob/received_events","type":"User","site_admin":false},"body":"Are you calculating `missing` again here?","created_at":"2018-02-26T04:13:09Z","updated_at":"2018-02-26T04:13:09Z","html_url":"https://github.com/home-assistant/home-assistant/pull/12453#discussion_r170490907","pull_request_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments/170490907"},"html":{"href":"https://github.com/home-assistant/home-assistant/pull/12453#discussion_r170490907"},"pull_request":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453"}}},"pull_request":{"url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453","id":169524146,"html_url":"https://github.com/home-assistant/home-assistant/pull/12453","diff_url":"https://github.com/home-assistant/home-assistant/pull/12453.diff","patch_url":"https://github.com/home-assistant/home-assistant/pull/12453.patch","issue_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/12453","number":12453,"state":"open","locked":false,"title":"Support serving of backend translations","user":{"login":"armills","id":1386547,"avatar_url":"https://avatars2.githubusercontent.com/u/1386547?v=4","gravatar_id":"","url":"https://api.github.com/users/armills","html_url":"https://github.com/armills","followers_url":"https://api.github.com/users/armills/followers","following_url":"https://api.github.com/users/armills/following{/other_user}","gists_url":"https://api.github.com/users/armills/gists{/gist_id}","starred_url":"https://api.github.com/users/armills/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/armills/subscriptions","organizations_url":"https://api.github.com/users/armills/orgs","repos_url":"https://api.github.com/users/armills/repos","events_url":"https://api.github.com/users/armills/events{/privacy}","received_events_url":"https://api.github.com/users/armills/received_events","type":"User","site_admin":false},"body":"## Description\r\nThis 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 https://github.com/home-assistant/architecture/issues/9\r\n\r\nStill quite a ways from MVP, but I wanted to get some better visibility into our path forward and potentially some early feedback.\r\n\r\n### Source files\r\nWe'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 named `strings.json`. Otherwise, they'll be stored in a file named `strings.<comonent_name>.json`. Examples: (`homeassistant/components/zwave/strings.json`, `homeassistant/components/sensor/strings.season.json`)\r\n\r\n### Lokalise downloads\r\nThe 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`)\r\n\r\n**Frontend PR:** https://github.com/home-assistant/home-assistant-polymer/pull/894\r\n\r\n**Lokalise temporary project:** https://lokalise.co/project/104514435a91b786c77a78.62627628/\r\n\r\n### TODO:\r\n- [x] Register platform translations with the frontend component when platforms are first setup\r\n- [x] Determine how translation sources will be checked in to the repo. (Equivalent of `src/translations/en.json` on the frontend.)\r\n- [x] Determine strategy to synchronize / distribute full translation sets from Lokalize through to the PyPI package.\r\n- [x] Tests\r\n- [ ] Create actual Lokalise project before merging\r\n\r\n### Future PR:\r\n- [ ] Allow clients to fetch translations for a single component\r\n- [ ] Notify clients when component with translations is set up\r\n- [ ] Determine backend fetch caching and strategy (Possibly include etags with the response)","created_at":"2018-02-16T00:38:30Z","updated_at":"2018-02-26T04:13:09Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c65a890832e0e2b29fbcef8b4a8598c90bc02e73","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":532490295,"url":"https://api.github.com/repos/home-assistant/home-assistant/labels/Ready%20for%20review","name":"Ready for review","color":"e99695","default":false},{"id":527871105,"url":"https://api.github.com/repos/home-assistant/home-assistant/labels/cla-signed","name":"cla-signed","color":"008000","default":false},{"id":817623580,"url":"https://api.github.com/repos/home-assistant/home-assistant/labels/component:%20frontend","name":"component: frontend","color":"ededed","default":false},{"id":451931292,"url":"https://api.github.com/repos/home-assistant/home-assistant/labels/core","name":"core","color":"c5def5","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453/commits","review_comments_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453/comments","review_comment_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments{/number}","comments_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/12453/comments","statuses_url":"https://api.github.com/repos/home-assistant/home-assistant/statuses/9edf9ddaa31b927c32c1741feca912eb3f44450e","head":{"label":"home-assistant:backend-translations","ref":"backend-translations","sha":"9edf9ddaa31b927c32c1741feca912eb3f44450e","user":{"login":"home-assistant","id":13844975,"avatar_url":"https://avatars3.githubusercontent.com/u/13844975?v=4","gravatar_id":"","url":"https://api.github.com/users/home-assistant","html_url":"https://github.com/home-assistant","followers_url":"https://api.github.com/users/home-assistant/followers","following_url":"https://api.github.com/users/home-assistant/following{/other_user}","gists_url":"https://api.github.com/users/home-assistant/gists{/gist_id}","starred_url":"https://api.github.com/users/home-assistant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/home-assistant/subscriptions","organizations_url":"https://api.github.com/users/home-assistant/orgs","repos_url":"https://api.github.com/users/home-assistant/repos","events_url":"https://api.github.com/users/home-assistant/events{/privacy}","received_events_url":"https://api.github.com/users/home-assistant/received_events","type":"Organization","site_admin":false},"repo":{"id":12888993,"name":"home-assistant","full_name":"home-assistant/home-assistant","owner":{"login":"home-assistant","id":13844975,"avatar_url":"https://avatars3.githubusercontent.com/u/13844975?v=4","gravatar_id":"","url":"https://api.github.com/users/home-assistant","html_url":"https://github.com/home-assistant","followers_url":"https://api.github.com/users/home-assistant/followers","following_url":"https://api.github.com/users/home-assistant/following{/other_user}","gists_url":"https://api.github.com/users/home-assistant/gists{/gist_id}","starred_url":"https://api.github.com/users/home-assistant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/home-assistant/subscriptions","organizations_url":"https://api.github.com/users/home-assistant/orgs","repos_url":"https://api.github.com/users/home-assistant/repos","events_url":"https://api.github.com/users/home-assistant/events{/privacy}","received_events_url":"https://api.github.com/users/home-assistant/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/home-assistant/home-assistant","description":":house_with_garden: Open-source home automation platform running on Python 3","fork":false,"url":"https://api.github.com/repos/home-assistant/home-assistant","forks_url":"https://api.github.com/repos/home-assistant/home-assistant/forks","keys_url":"https://api.github.com/repos/home-assistant/home-assistant/keys{/key_id}","collaborators_url":"https://api.github.com/repos/home-assistant/home-assistant/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/home-assistant/home-assistant/teams","hooks_url":"https://api.github.com/repos/home-assistant/home-assistant/hooks","issue_events_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/events{/number}","events_url":"https://api.github.com/repos/home-assistant/home-assistant/events","assignees_url":"https://api.github.com/repos/home-assistant/home-assistant/assignees{/user}","branches_url":"https://api.github.com/repos/home-assistant/home-assistant/branches{/branch}","tags_url":"https://api.github.com/repos/home-assistant/home-assistant/tags","blobs_url":"https://api.github.com/repos/home-assistant/home-assistant/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/home-assistant/home-assistant/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/home-assistant/home-assistant/git/refs{/sha}","trees_url":"https://api.github.com/repos/home-assistant/home-assistant/git/trees{/sha}","statuses_url":"https://api.github.com/repos/home-assistant/home-assistant/statuses/{sha}","languages_url":"https://api.github.com/repos/home-assistant/home-assistant/languages","stargazers_url":"https://api.github.com/repos/home-assistant/home-assistant/stargazers","contributors_url":"https://api.github.com/repos/home-assistant/home-assistant/contributors","subscribers_url":"https://api.github.com/repos/home-assistant/home-assistant/subscribers","subscription_url":"https://api.github.com/repos/home-assistant/home-assistant/subscription","commits_url":"https://api.github.com/repos/home-assistant/home-assistant/commits{/sha}","git_commits_url":"https://api.github.com/repos/home-assistant/home-assistant/git/commits{/sha}","comments_url":"https://api.github.com/repos/home-assistant/home-assistant/comments{/number}","issue_comment_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/comments{/number}","contents_url":"https://api.github.com/repos/home-assistant/home-assistant/contents/{+path}","compare_url":"https://api.github.com/repos/home-assistant/home-assistant/compare/{base}...{head}","merges_url":"https://api.github.com/repos/home-assistant/home-assistant/merges","archive_url":"https://api.github.com/repos/home-assistant/home-assistant/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/home-assistant/home-assistant/downloads","issues_url":"https://api.github.com/repos/home-assistant/home-assistant/issues{/number}","pulls_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls{/number}","milestones_url":"https://api.github.com/repos/home-assistant/home-assistant/milestones{/number}","notifications_url":"https://api.github.com/repos/home-assistant/home-assistant/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/home-assistant/home-assistant/labels{/name}","releases_url":"https://api.github.com/repos/home-assistant/home-assistant/releases{/id}","deployments_url":"https://api.github.com/repos/home-assistant/home-assistant/deployments","created_at":"2013-09-17T07:29:48Z","updated_at":"2018-02-26T03:12:34Z","pushed_at":"2018-02-26T03:28:42Z","git_url":"git://github.com/home-assistant/home-assistant.git","ssh_url":"git@github.com:home-assistant/home-assistant.git","clone_url":"https://github.com/home-assistant/home-assistant.git","svn_url":"https://github.com/home-assistant/home-assistant","homepage":"https://home-assistant.io","size":78984,"stargazers_count":12794,"watchers_count":12794,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":3689,"mirror_url":null,"archived":false,"open_issues_count":845,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":3689,"open_issues":845,"watchers":12794,"default_branch":"dev"}},"base":{"label":"home-assistant:dev","ref":"dev","sha":"6d431c3fc3f6a8c9bf73c73396ddd0c47dd7aeff","user":{"login":"home-assistant","id":13844975,"avatar_url":"https://avatars3.githubusercontent.com/u/13844975?v=4","gravatar_id":"","url":"https://api.github.com/users/home-assistant","html_url":"https://github.com/home-assistant","followers_url":"https://api.github.com/users/home-assistant/followers","following_url":"https://api.github.com/users/home-assistant/following{/other_user}","gists_url":"https://api.github.com/users/home-assistant/gists{/gist_id}","starred_url":"https://api.github.com/users/home-assistant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/home-assistant/subscriptions","organizations_url":"https://api.github.com/users/home-assistant/orgs","repos_url":"https://api.github.com/users/home-assistant/repos","events_url":"https://api.github.com/users/home-assistant/events{/privacy}","received_events_url":"https://api.github.com/users/home-assistant/received_events","type":"Organization","site_admin":false},"repo":{"id":12888993,"name":"home-assistant","full_name":"home-assistant/home-assistant","owner":{"login":"home-assistant","id":13844975,"avatar_url":"https://avatars3.githubusercontent.com/u/13844975?v=4","gravatar_id":"","url":"https://api.github.com/users/home-assistant","html_url":"https://github.com/home-assistant","followers_url":"https://api.github.com/users/home-assistant/followers","following_url":"https://api.github.com/users/home-assistant/following{/other_user}","gists_url":"https://api.github.com/users/home-assistant/gists{/gist_id}","starred_url":"https://api.github.com/users/home-assistant/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/home-assistant/subscriptions","organizations_url":"https://api.github.com/users/home-assistant/orgs","repos_url":"https://api.github.com/users/home-assistant/repos","events_url":"https://api.github.com/users/home-assistant/events{/privacy}","received_events_url":"https://api.github.com/users/home-assistant/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/home-assistant/home-assistant","description":":house_with_garden: Open-source home automation platform running on Python 3","fork":false,"url":"https://api.github.com/repos/home-assistant/home-assistant","forks_url":"https://api.github.com/repos/home-assistant/home-assistant/forks","keys_url":"https://api.github.com/repos/home-assistant/home-assistant/keys{/key_id}","collaborators_url":"https://api.github.com/repos/home-assistant/home-assistant/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/home-assistant/home-assistant/teams","hooks_url":"https://api.github.com/repos/home-assistant/home-assistant/hooks","issue_events_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/events{/number}","events_url":"https://api.github.com/repos/home-assistant/home-assistant/events","assignees_url":"https://api.github.com/repos/home-assistant/home-assistant/assignees{/user}","branches_url":"https://api.github.com/repos/home-assistant/home-assistant/branches{/branch}","tags_url":"https://api.github.com/repos/home-assistant/home-assistant/tags","blobs_url":"https://api.github.com/repos/home-assistant/home-assistant/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/home-assistant/home-assistant/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/home-assistant/home-assistant/git/refs{/sha}","trees_url":"https://api.github.com/repos/home-assistant/home-assistant/git/trees{/sha}","statuses_url":"https://api.github.com/repos/home-assistant/home-assistant/statuses/{sha}","languages_url":"https://api.github.com/repos/home-assistant/home-assistant/languages","stargazers_url":"https://api.github.com/repos/home-assistant/home-assistant/stargazers","contributors_url":"https://api.github.com/repos/home-assistant/home-assistant/contributors","subscribers_url":"https://api.github.com/repos/home-assistant/home-assistant/subscribers","subscription_url":"https://api.github.com/repos/home-assistant/home-assistant/subscription","commits_url":"https://api.github.com/repos/home-assistant/home-assistant/commits{/sha}","git_commits_url":"https://api.github.com/repos/home-assistant/home-assistant/git/commits{/sha}","comments_url":"https://api.github.com/repos/home-assistant/home-assistant/comments{/number}","issue_comment_url":"https://api.github.com/repos/home-assistant/home-assistant/issues/comments{/number}","contents_url":"https://api.github.com/repos/home-assistant/home-assistant/contents/{+path}","compare_url":"https://api.github.com/repos/home-assistant/home-assistant/compare/{base}...{head}","merges_url":"https://api.github.com/repos/home-assistant/home-assistant/merges","archive_url":"https://api.github.com/repos/home-assistant/home-assistant/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/home-assistant/home-assistant/downloads","issues_url":"https://api.github.com/repos/home-assistant/home-assistant/issues{/number}","pulls_url":"https://api.github.com/repos/home-assistant/home-assistant/pulls{/number}","milestones_url":"https://api.github.com/repos/home-assistant/home-assistant/milestones{/number}","notifications_url":"https://api.github.com/repos/home-assistant/home-assistant/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/home-assistant/home-assistant/labels{/name}","releases_url":"https://api.github.com/repos/home-assistant/home-assistant/releases{/id}","deployments_url":"https://api.github.com/repos/home-assistant/home-assistant/deployments","created_at":"2013-09-17T07:29:48Z","updated_at":"2018-02-26T03:12:34Z","pushed_at":"2018-02-26T03:28:42Z","git_url":"git://github.com/home-assistant/home-assistant.git","ssh_url":"git@github.com:home-assistant/home-assistant.git","clone_url":"https://github.com/home-assistant/home-assistant.git","svn_url":"https://github.com/home-assistant/home-assistant","homepage":"https://home-assistant.io","size":78984,"stargazers_count":12794,"watchers_count":12794,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":3689,"mirror_url":null,"archived":false,"open_issues_count":845,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":3689,"open_issues":845,"watchers":12794,"default_branch":"dev"}},"_links":{"self":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453"},"html":{"href":"https://github.com/home-assistant/home-assistant/pull/12453"},"issue":{"href":"https://api.github.com/repos/home-assistant/home-assistant/issues/12453"},"comments":{"href":"https://api.github.com/repos/home-assistant/home-assistant/issues/12453/comments"},"review_comments":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453/comments"},"review_comment":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/home-assistant/home-assistant/pulls/12453/commits"},"statuses":{"href":"https://api.github.com/repos/home-assistant/home-assistant/statuses/9edf9ddaa31b927c32c1741feca912eb3f44450e"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"balloob"}}