Commit bb19a5f3 authored by cduvall@chromium.org's avatar cduvall@chromium.org

Extensions Docs Server: Internationalized samples

The samples page can now handle names and descriptions of the form '__MSG_...',
and the locale is chosen based on the Accept-Languages HTTP header.

BUG=131095

Review URL: https://chromiumcodereview.appspot.com/10804036

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148370 0039d316-1c4b-4281-b951-d872f2087c98
parent 6b8f4a92
......@@ -85,15 +85,15 @@ class Handler(webapp.RequestHandler):
PUBLIC_TEMPLATE_PATH)
intro_data_source = IntroDataSource(cache_builder,
[INTRO_PATH, ARTICLE_PATH])
samples_data_source = SamplesDataSource(file_system,
cache_builder,
EXAMPLES_PATH)
samples_data_source_factory = SamplesDataSource.Factory(file_system,
cache_builder,
EXAMPLES_PATH)
template_data_source_factory = TemplateDataSource.Factory(
branch,
api_data_source,
api_list_data_source,
intro_data_source,
samples_data_source,
samples_data_source_factory,
cache_builder,
PUBLIC_TEMPLATE_PATH,
PRIVATE_TEMPLATE_PATH)
......@@ -134,11 +134,7 @@ class Handler(webapp.RequestHandler):
self.response)
def main():
handlers = [
('/.*', Handler),
]
run_wsgi_app(webapp.WSGIApplication(handlers, debug=False))
run_wsgi_app(webapp.WSGIApplication([('/.*', Handler)], debug=False))
if __name__ == '__main__':
main()
......@@ -2,6 +2,16 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os
def _ProcessFileData(data, path):
if os.path.splitext(path)[-1] not in ['.js', '.html', '.json']:
return data
try:
return unicode(data, 'utf-8')
except:
return unicode(data, 'latin-1')
class FileSystem(object):
"""A FileSystem interface that can read files and directories.
"""
......
......@@ -26,9 +26,6 @@ class FileSystemCache(object):
self._populate_function = populate_function
self._cache = {}
def _FetchFile(self, filename):
return self._file_system.Read([filename]).Get()[filename]
def _RecursiveList(self, files):
all_files = files[:]
dirs = {}
......@@ -49,7 +46,7 @@ class FileSystemCache(object):
self._cache.pop(path)
else:
return self._cache[path]._cache_data
cache_data = self._FetchFile(path)
cache_data = self._file_system.ReadSingle(path)
self._cache[path] = self._CacheEntry(self._populate_function(cache_data),
version)
return self._cache[path]._cache_data
......@@ -66,7 +63,8 @@ class FileSystemCache(object):
self._cache.pop(path)
else:
return self._cache[path]._cache_data
cache_data = self._RecursiveList([path + f for f in self._FetchFile(path)])
cache_data = self._RecursiveList(
[path + f for f in self._file_system.ReadSingle(path)])
self._cache[path] = self._CacheEntry(self._populate_function(cache_data),
version)
return self._cache[path]._cache_data
......@@ -60,16 +60,13 @@ class IntroDataSource(object):
self._base_paths = base_paths
def _MakeIntroDict(self, intro):
try:
parser = _IntroParser()
parser.feed(intro)
return {
'intro': Handlebar(intro),
'toc': parser.toc,
'title': parser.page_title
}
except Exception as e:
logging.info(e)
parser = _IntroParser()
parser.feed(intro)
return {
'intro': Handlebar(intro),
'toc': parser.toc,
'title': parser.page_title
}
def __getitem__(self, key):
return self.get(key)
......
......@@ -4,10 +4,10 @@
import os
from file_system import FileSystem
import file_system
from future import Future
class LocalFileSystem(FileSystem):
class LocalFileSystem(file_system.FileSystem):
"""FileSystem implementation which fetches resources from the local
filesystem.
"""
......@@ -19,7 +19,7 @@ class LocalFileSystem(FileSystem):
def _ReadFile(self, filename):
with open(os.path.join(self._base_path, filename), 'r') as f:
return f.read()
return file_system._ProcessFileData(f.read(), filename)
def _ListDir(self, dir_name):
all_files = []
......
......@@ -28,7 +28,7 @@ class LocalFileSystemTest(unittest.TestCase):
for i in range(7):
expected.append('file%d.html' % i)
self.assertEqual(expected,
sorted(self._file_system.Read(['list/']).Get()['list/']))
sorted(self._file_system.ReadSingle('list/')))
if __name__ == '__main__':
unittest.main()
......@@ -32,16 +32,14 @@ class LocalFileSystemTest(unittest.TestCase):
expected = ['dir/']
for i in range(7):
expected.append('file%d.html' % i)
self.assertEqual(
expected,
sorted(self._file_system.Read(['list/']).Get()['list/']))
self.assertEqual(expected,
sorted(self._file_system.ReadSingle('list/')))
expected.remove('file0.html')
self._memcache.Set('list/',
(expected, self._file_system.Stat('list/')),
memcache.MEMCACHE_FILE_SYSTEM_READ)
self.assertEqual(
expected,
sorted(self._file_system.Read(['list/']).Get()['list/']))
self.assertEqual(expected,
sorted(self._file_system.ReadSingle('list/')))
if __name__ == '__main__':
unittest.main()
......@@ -3,6 +3,7 @@
# found in the LICENSE file.
import json
import logging
import re
DEFAULT_ICON_PATH = '/static/images/sample-default-icon.png'
......@@ -10,74 +11,139 @@ DEFAULT_ICON_PATH = '/static/images/sample-default-icon.png'
class SamplesDataSource(object):
"""Constructs a list of samples and their respective files and api calls.
"""
def __init__(self, fetcher, cache_builder, samples_path):
self._fetcher = fetcher
self._cache = cache_builder.build(self._MakeSamplesList)
self._samples_path = samples_path
def _GetApiItems(self, js_file):
return set(re.findall('(chrome\.[a-zA-Z0-9\.]+)', js_file))
class Factory(object):
"""A factory to create SamplesDataSource instances bound to individual
Requests.
"""
def __init__(self, file_system, cache_builder, samples_path):
self._file_system = file_system
self._cache = cache_builder.build(self._MakeSamplesList)
self._samples_path = samples_path
def Create(self, request):
"""Returns a new SamplesDataSource bound to |request|.
"""
return SamplesDataSource(self._cache,
self._samples_path,
request)
def _MakeApiLink(self, prefix, item):
api, name = item.replace('chrome.', '').split('.', 1)
return api + '.html#' + prefix + '-' + name
def _GetApiItems(self, js_file):
return set(re.findall('(chrome\.[a-zA-Z0-9\.]+)', js_file))
def _GetDataFromManifest(self, path):
manifest_path = path + '/manifest.json'
manifest = self._fetcher.Read([manifest_path]).Get()[manifest_path]
manifest_json = json.loads(manifest)
return {
'name': manifest_json.get('name'),
'description': manifest_json.get('description'),
'icon': manifest_json.get('icons', {}).get('128', None)
}
def _MakeApiLink(self, prefix, item):
api, name = item.replace('chrome.', '').split('.', 1)
return api + '.html#' + prefix + '-' + name
def _MakeSamplesList(self, files):
samples_list = []
for filename in sorted(files):
if filename.rsplit('/')[-1] != 'manifest.json':
continue
# This is a little hacky, but it makes a sample page.
sample_path = filename.rsplit('/', 1)[-2]
sample_files = filter(lambda x: x.startswith(sample_path + '/'), files)
js_files = filter(lambda x: x.endswith('.js'), sample_files)
js_contents = self._fetcher.Read(js_files).Get()
api_items = set()
for js in js_contents.values():
api_items.update(self._GetApiItems(js))
def _GetDataFromManifest(self, path):
manifest = self._file_system.ReadSingle(path + '/manifest.json')
manifest_json = json.loads(manifest)
l10n_data = {
'name': manifest_json.get('name', ''),
'description': manifest_json.get('description', ''),
'icon': manifest_json.get('icons', {}).get('128', None),
'default_locale': manifest_json.get('default_locale', None),
'locales': {}
}
if not l10n_data['default_locale']:
return l10n_data
locales_path = path + '/_locales/'
locales_dir = self._file_system.ReadSingle(locales_path)
if locales_dir:
locales_files = self._file_system.Read(
[locales_path + f + 'messages.json' for f in locales_dir]).Get()
locales_json = [(path, json.loads(contents))
for path, contents in locales_files.iteritems()]
for path, json_ in locales_json:
l10n_data['locales'][path[len(locales_path):].split('/')[0]] = json_
return l10n_data
api_calls = []
for item in api_items:
if len(item.split('.')) < 3:
def _MakeSamplesList(self, files):
samples_list = []
for filename in sorted(files):
if filename.rsplit('/')[-1] != 'manifest.json':
continue
if item.endswith('.addListener'):
item = item.replace('.addListener', '')
api_calls.append({
'name': item,
'link': self._MakeApiLink('event', item)
})
# This is a little hacky, but it makes a sample page.
sample_path = filename.rsplit('/', 1)[-2]
sample_files = [path for path in files
if path.startswith(sample_path + '/')]
js_files = [path for path in sample_files if path.endswith('.js')]
js_contents = self._file_system.Read(js_files).Get()
api_items = set()
for js in js_contents.values():
api_items.update(self._GetApiItems(js))
api_calls = []
for item in api_items:
if len(item.split('.')) < 3:
continue
if item.endswith('.addListener'):
item = item.replace('.addListener', '')
api_calls.append({
'name': item,
'link': self._MakeApiLink('event', item)
})
else:
api_calls.append({
'name': item,
'link': self._MakeApiLink('method', item)
})
l10n_data = self._GetDataFromManifest(sample_path)
sample_base_path = sample_path.split('/', 1)[1]
if l10n_data['icon'] is None:
icon_path = DEFAULT_ICON_PATH
else:
api_calls.append({
'name': item,
'link': self._MakeApiLink('method', item)
})
samples_info = self._GetDataFromManifest(sample_path)
sample_base_path = sample_path.split('/', 1)[1]
if samples_info['icon'] is None:
icon_path = DEFAULT_ICON_PATH
else:
icon_path = sample_base_path + '/' + samples_info['icon']
samples_info.update({
'icon': icon_path,
'path': sample_base_path,
'files': [f.replace(sample_path + '/', '') for f in sample_files],
'api_calls': api_calls
})
samples_list.append(samples_info)
return samples_list
icon_path = sample_base_path + '/' + l10n_data['icon']
l10n_data.update({
'icon': icon_path,
'path': sample_base_path,
'files': [f.replace(sample_path + '/', '') for f in sample_files],
'api_calls': api_calls
})
samples_list.append(l10n_data)
return samples_list
def __init__(self, cache, samples_path, request):
self._cache = cache
self._samples_path = samples_path
self._request = request
def _GetAcceptedLanguages(self):
accept_language = self._request.headers.get('Accept-Language', None)
if accept_language is None:
return []
return [lang_with_q.split(';')[0].strip()
for lang_with_q in accept_language.split(',')]
def __getitem__(self, key):
return self.get(key)
def get(self, key):
return self._cache.GetFromFileListing(self._samples_path + '/')
samples_list = self._cache.GetFromFileListing(self._samples_path + '/')
return_list = []
for dict_ in samples_list:
name = dict_['name']
description = dict_['description']
if name.startswith('__MSG_') or description.startswith('__MSG_'):
try:
# Copy the sample dict so we don't change the dict in the cache.
sample_data = dict_.copy()
name_key = name[len('__MSG_'):-len('__')]
description_key = description[len('__MSG_'):-len('__')]
locale = sample_data['default_locale']
for lang in self._GetAcceptedLanguages():
if lang in sample_data['locales']:
locale = lang
break
locale_data = sample_data['locales'][locale]
sample_data['name'] = locale_data[name_key]['message']
sample_data['description'] = locale_data[description_key]['message']
except Exception as e:
logging.error(e)
# Revert the sample to the original dict.
sample_data = dict_
return_list.append(sample_data)
else:
return_list.append(dict_)
return return_list
......@@ -282,7 +282,7 @@ span.displayModeWarning {
#gc-toc {
margin: 0;
width: 180px;
position: absolute;
float: left;
}
#gc-toc * {
......
......@@ -5,10 +5,10 @@
import re
import xml.dom.minidom as xml
from file_system import FileSystem
import file_system
from future import Future
class SubversionFileSystem(FileSystem):
class SubversionFileSystem(file_system.FileSystem):
"""Class to fetch resources from src.chromium.org.
"""
def __init__(self, fetcher):
......@@ -45,7 +45,7 @@ class _AsyncFetchFuture(object):
elif path.endswith('/'):
self._value[path] = self._ListDir(result.content)
else:
self._value[path] = result.content
self._value[path] = file_system._ProcessFileData(result.content, path)
if self._error is not None:
raise self._error
return self._value
......
......@@ -29,7 +29,7 @@ class SubversionFileSystemTest(unittest.TestCase):
for i in range(7):
expected.append('file%d.html' % i)
self.assertEqual(expected,
sorted(self._file_system.Read(['list/']).Get()['list/']))
sorted(self._file_system.ReadSingle('list/')))
def testStat(self):
# Value is hard-coded into FakeUrlFetcher.
......
......@@ -42,7 +42,7 @@ class TemplateDataSource(object):
api_data_source,
api_list_data_source,
intro_data_source,
samples_data_source,
samples_data_source_factory,
cache_builder,
public_template_path,
private_template_path):
......@@ -52,7 +52,7 @@ class TemplateDataSource(object):
self._api_data_source = api_data_source
self._api_list_data_source = api_list_data_source
self._intro_data_source = intro_data_source
self._samples_data_source = samples_data_source
self._samples_data_source_factory = samples_data_source_factory
self._cache = cache_builder.build(Handlebar)
self._public_template_path = public_template_path
self._private_template_path = private_template_path
......@@ -65,7 +65,7 @@ class TemplateDataSource(object):
self._api_data_source,
self._api_list_data_source,
self._intro_data_source,
self._samples_data_source,
self._samples_data_source_factory,
self._cache,
self._public_template_path,
self._private_template_path,
......@@ -77,7 +77,7 @@ class TemplateDataSource(object):
api_data_source,
api_list_data_source,
intro_data_source,
samples_data_source,
samples_data_source_factory,
cache,
public_template_path,
private_template_path,
......@@ -87,7 +87,7 @@ class TemplateDataSource(object):
self._api_data_source = api_data_source
self._api_list_data_source = api_list_data_source
self._intro_data_source = intro_data_source
self._samples_data_source = samples_data_source
self._samples_data_source = samples_data_source_factory.Create(request)
self._cache = cache
self._public_template_path = public_template_path
self._private_template_path = private_template_path
......
......@@ -13,16 +13,19 @@ from template_data_source import TemplateDataSource
from third_party.handlebar import Handlebar
class _FakeRequest(object):
def __init__(self):
pass
class _FakeSamplesDataSource(object):
def Create(self, request):
return {}
class TemplateDataSourceTest(unittest.TestCase):
def setUp(self):
self._base_path = os.path.join('test_data', 'template_data_source')
self._fake_api_data_source = {}
self._fake_api_list_data_source = {}
self._fake_intro_data_source = {}
self._fake_samples_data_source = {}
self._fake_samples_data_source = _FakeSamplesDataSource()
def _ReadLocalFile(self, filename):
with open(os.path.join(self._base_path, filename), 'r') as f:
......
......@@ -37,7 +37,7 @@ relating to the app.
Packaged apps have no traditional chrome:
the omnibox (address bar), tab strip,
and other browser interface elements no longer appear.
Like native apps, they dont live within the browser.
Like native apps, they don't live within the browser.
When launched, packaged apps can open in windows
that look like this (and you can style
your windows in all different ways):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment