Skip to content
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

Localized content overrides in YAML #266

Merged
merged 5 commits into from
Aug 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 25 additions & 23 deletions grow/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import StringIO
except ImportError:
from io import StringIO
from boltons import iterutils
import bs4
import csv as csv_lib
import functools
Expand All @@ -16,11 +17,11 @@
import logging
import os
import re
import urllib
import sys
import threading
import time
import translitcodec
import urllib
import yaml

# The CLoader implementation of the PyYaml loader is orders of magnitutde
Expand All @@ -33,7 +34,9 @@
from yaml import Loader as yaml_Loader


LOCALIZED_KEY_REGEX = re.compile('(.*)@([\w|-]+)$')
SENTINEL = object()
SLUG_REGEX = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')


class Error(Exception):
Expand Down Expand Up @@ -269,12 +272,11 @@ def dump_yaml(obj):
obj, allow_unicode=True, width=800, default_flow_style=False)


_slug_re = re.compile(r'[\t !"#$%&\'()*\-/<=>?@\[\\\]^_`{|},.]+')
def slugify(text, delim=u'-'):
if not isinstance(text, basestring):
text = str(text)
result = []
for word in _slug_re.split(text.lower()):
for word in SLUG_REGEX.split(text.lower()):
word = word.encode('translit/long')
if word:
result.append(word)
Expand All @@ -296,30 +298,30 @@ def default(self, obj):


@memoize
def untag_fields(fields):
def untag_fields(fields, locale=None):
"""Untags fields, handling translation priority."""
untagged_keys_to_add = {}
nodes_and_keys_to_add = []
nodes_and_keys_to_remove = []
def callback(item, key, node):

updated_localized_paths = set()

def visit(path, key, value):
if (path, key) in updated_localized_paths:
return False
if not isinstance(key, basestring):
return
return key, value
if key.endswith('@#'):
nodes_and_keys_to_remove.append((node, key))
return False
if key.endswith('@'):
untagged_key = key.rstrip('@')
content = item
nodes_and_keys_to_remove.append((node, key))
untagged_keys_to_add[untagged_key] = True
nodes_and_keys_to_add.append((node, untagged_key, content))
walk(fields, callback)
for node, key in nodes_and_keys_to_remove:
if isinstance(node, dict):
del node[key]
for node, untagged_key, content in nodes_and_keys_to_add:
if isinstance(node, dict):
node[untagged_key] = content
return fields
key = key[:-1]
match = LOCALIZED_KEY_REGEX.match(key)
if not match:
return key, value
untagged_key, locale_from_key = match.groups()
if locale_from_key != locale:
return False
updated_localized_paths.add((path, untagged_key))
return untagged_key, value

return iterutils.remap(fields, visit=visit)


def LocaleIterator(iterator, locale):
Expand Down
185 changes: 183 additions & 2 deletions grow/common/utils_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from grow.testing import testing
from grow.common.sdk_utils import get_this_version, LatestVersionCheckError
from . import utils
import unittest
import semantic_version
import copy
import mock
import semantic_version
import unittest


class UtilsTestCase(unittest.TestCase):
Expand Down Expand Up @@ -46,6 +47,186 @@ def test_version_enforcement(self):
with self.assertRaises(LatestVersionCheckError):
pod = testing.create_test_pod()

def test_untag_fields(self):
fields_to_test = {
'title': 'value-none',
'title@fr': 'value-fr',
'list': [
{
'list-item-title': 'value-none',
'list-item-title@fr': 'value-fr',
},
],
'sub-nested': {
'sub-nested': {
'nested@': 'sub-sub-nested-value',
},
},
'nested': {
'nested-none': 'nested-value-none',
'nested-title@': 'nested-value-none',
},
'nested@fr': {
'nested-title@': 'nested-value-fr',
},
'list@de': [
'list-item-de',
]
}
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'title': 'value-fr',
'list': [{'list-item-title': 'value-fr'},],
'nested': {'nested-title': 'nested-value-fr',},
'sub-nested': {
'sub-nested': {
'nested': 'sub-sub-nested-value',
},
},
}, utils.untag_fields(fields, locale='fr'))

fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'title': 'value-none',
'list': ['list-item-de',],
'nested': {
'nested-none': 'nested-value-none',
'nested-title': 'nested-value-none',
},
'sub-nested': {
'sub-nested': {
'nested': 'sub-sub-nested-value',
},
},
}, utils.untag_fields(fields, locale='de'))

fields_to_test = {
'foo': 'bar-base',
'foo@de': 'bar-de',
'foo@fr': 'bar-fr',
'nested': {
'nested': 'nested-base',
'nested@fr': 'nested-fr',
},
}
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'foo': 'bar-fr',
'nested': {
'nested': 'nested-fr',
},
}, utils.untag_fields(fields, locale='fr'))
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'foo': 'bar-de',
'nested': {
'nested': 'nested-base',
},
}, utils.untag_fields(fields, locale='de'))

fields_to_test = {
'list': [
{
'item': 'value-1',
'item@de': 'value-1-de',
'item@fr': 'value-1-fr',
},
{
'item': 'value-2',
},
{
'item@fr': 'value-3-fr',
},
]
}
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'list': [
{
'item': 'value-1-fr',
},
{
'item': 'value-2',
},
{
'item': 'value-3-fr',
},
]
}, utils.untag_fields(fields, locale='fr'))
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'list': [
{
'item': 'value-1-de',
},
{
'item': 'value-2',
},
{},
]
}, utils.untag_fields(fields, locale='de'))
self.assertDictEqual({
'list': [
{
'item': 'value-1',
},
{
'item': 'value-2',
},
{},
]
}, utils.untag_fields(fields, locale='ja'))

fields_to_test = {
'$view': '/views/base.html',
'$view@ja': '/views/base-ja.html',
'qaz': 'qux',
'qaz@ja': 'qux-ja',
'qaz@de': 'qux-de',
'qaz@ja': 'qux-ja',
'foo': 'bar-base',
'foo@en': 'bar-en',
'foo@de': 'bar-de',
'foo@ja': 'bar-ja',
'nested': {
'nested': 'nested-base',
'nested@ja': 'nested-ja',
},
}
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'$view': '/views/base-ja.html',
'qaz': 'qux-ja',
'foo': 'bar-ja',
'nested': {
'nested': 'nested-ja',
},
}, utils.untag_fields(fields, locale='ja'))
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'$view': '/views/base.html',
'qaz': 'qux-de',
'foo': 'bar-de',
'nested': {
'nested': 'nested-base',
},
}, utils.untag_fields(fields, locale='de'))

fields_to_test = {
'foo@': 'bar',
'foo@fr@': 'bar-fr',
}
fields = copy.deepcopy(fields_to_test)
self.assertDictEqual({
'foo': 'bar',
}, utils.untag_fields(fields))
self.assertDictEqual({
'foo': 'bar',
}, utils.untag_fields(fields, locale='de'))
self.assertDictEqual({
'foo': 'bar-fr',
}, utils.untag_fields(fields, locale='fr'))


if __name__ == '__main__':
unittest.main()
12 changes: 11 additions & 1 deletion grow/pods/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def __init__(self, pod_path, _pod):
self.collection_path = regex.sub('', pod_path).strip('/')
self.pod_path = pod_path
self.basename = os.path.basename(self.collection_path)
self._default_locale = _pod.podspec.default_locale
self._blueprint_path = os.path.join(
self.pod_path, Collection.BLUEPRINT_PATH)

Expand Down Expand Up @@ -85,6 +84,17 @@ def fields(self):
def tagged_fields(self):
return copy.deepcopy(self.yaml)

@utils.cached_property
def default_locale(self):
if self.localization and 'default_locale' in self.localization:
locale = self.localization['default_locale']
else:
locale = self.pod.podspec.default_locale
locale = locales.Locale.parse(locale)
if locale:
locale.set_alias(self.pod)
return locale

@classmethod
def list(cls, pod):
items = []
Expand Down
19 changes: 8 additions & 11 deletions grow/pods/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,18 @@ def _clean_basename(cls, pod_path):
def default_locale(self):
if ('$localization' in self.fields
and 'default_locale' in self.fields['$localization']):
locale = self.fields['$localization']['default_locale']
elif (self.collection.localization
and 'default_locale' in self.collection.localization):
locale = self.collection.localization['default_locale']
else:
locale = self.pod.podspec.default_locale
locale = locales.Locale.parse(locale)
if locale:
locale.set_alias(self.pod)
return locale
identifier = self.fields['$localization']['default_locale']
locale = locales.Locale.parse(identifier)
if locale:
locale.set_alias(self.pod)
return locale
return self.collection.default_locale

@utils.cached_property
def fields(self):
identifier = self.locale or self.collection.default_locale
tagged_fields = self.get_tagged_fields()
fields = utils.untag_fields(tagged_fields)
fields = utils.untag_fields(tagged_fields, locale=str(identifier))
return {} if not fields else fields

def get_tagged_fields(self):
Expand Down