diff --git a/lib/index.coffee b/lib/index.coffee index 9bc58a2..7faa3a9 100644 --- a/lib/index.coffee +++ b/lib/index.coffee @@ -40,7 +40,7 @@ module.exports = (opts) -> @roots.config.locals.asset = asset_view_helper setup: -> - configure_content(opts.content_types).with(@) + configure_content(opts).with(@) .then(get_all_content) .tap(set_urls) .then(transform_entries) @@ -56,18 +56,70 @@ module.exports = (opts) -> * @return {Promise} - returns an array of configured content types ### - configure_content = (types) -> - if _.isPlainObject(types) then types = reconfigure_alt_type_config(types) - W.map types, (t) -> - if not t.id then return W.reject(errors.no_type_id) - t.filters ?= {} - if (not t.name || (t.template && not t.path)) - return W client.contentType(t.id).then (res) -> - t.name ?= pluralize(S(res.name).toLowerCase().underscore().s) - if t.template - t.path ?= (e) -> "#{t.name}/#{S(e[res.displayField]).slugify().s}" - return t - return W.resolve(t) + configure_content = (opts) -> + types = opts.content_types + locales = opts.locale + lPrefixes = opts.locales_prefix + + isWildcard = -> # if locales is wildcard `*`, fetch & set locales + return W( + if locales is "*" + fetch_all_locales().then (res) -> + locales = res + W.resolve locales + else + W.resolve + ) + + reconfigObj = -> + types = reconfigure_alt_type_config(types) if _.isPlainObject(types) + + localesArray = -> + if _.isArray(locales) # duplicate & update type to contain locale's data + for locale in locales + for t in types + unless t.locale? # type's locale overrides global locale + tmp = _.clone(t, true) # create clone + tmp.locale = locale + tmp.prefix = lPrefixes?[locale] ? "#{locale.replace(/-/,'_')}_" + types.push tmp # add to types + else + # set prefix, only if it isn't set + t.prefix ?= lPrefixes?[locale] ? "#{locale.replace(/-/,'_')}_" + + types = _.remove types, (t) -> t.locale? # remove dupes w/o locale + else + if _.isString opts.locale + global_locale = true + + isWildcard() + .then reconfigObj + .then localesArray + .then -> + W.map types, (t) -> + if not t.id then return W.reject(errors.no_type_id) + t.filters ?= {} + + if (not t.name || (t.template && not t.path)) + return W client.contentType(t.id).then (res) -> + t.name ?= pluralize(S(res.name).toLowerCase().underscore().s) + + unless _.isUndefined lPrefixes + t.name = t.prefix + t.name + + if t.template or lPrefixes? + t.path ?= (e) -> + "#{t.name}/#{S(e[res.displayField]).slugify().s}" + + return t + + unless _.isUndefined lPrefixes + t.name = t.prefix + t.name + + if global_locale? then t.locale or= opts.locale + + return W.resolve(t) + ###* * Reconfigures content types set in app.coffee using an object instead of @@ -104,11 +156,30 @@ module.exports = (opts) -> fetch_content = (type) -> W( - client.entries( - _.merge(type.filters, content_type: type.id, include: 10) + client.entries(_.merge( + type.filters, + content_type: type.id, + include: 10, + locale: type.locale + ) ) ) + ###* + * Fetch all locales in space + * Used when `*` is used in opts.locales + * @return {Array} - returns array of locales + ### + + fetch_all_locales = -> + W(client.space() + .then (res) -> + locales = [] + for locale in res.locales + locales.push locale.code + W.resolve locales + ) + ###* * Formats raw response from Contentful * @param {Object} content - entries API response for a content type @@ -125,7 +196,7 @@ module.exports = (opts) -> format_entry = (e) -> if _.has(e.fields, 'sys') then return W.reject(errors.sys_conflict) - _.assign(_.omit(e, 'fields'), e.fields) + _.assign(_.omit(_.omit(e, 'sys'), 'fields'), e.fields) ###* * Sets `_url` and `_urls` properties on content with single entry views @@ -150,8 +221,10 @@ module.exports = (opts) -> ### set_locals = (types) -> - W.map types, (t) => - @roots.config.locals.contentful[t.name] = t.content + contentful = @roots.config.locals.contentful + W.map types, (t) -> + if contentful[t.name] then contentful[t.name].push t.content[0] + else contentful[t.name] = t.content ###* * Transforms every type with content with the user provided callback @@ -160,9 +233,9 @@ module.exports = (opts) -> ### transform_entries = (types) -> - W.map types, (t) => + W.map types, (t) -> if t.transform - W.map t.content, (entry) => + W.map t.content, (entry) -> W(entry, t.transform) W.resolve(t) @@ -173,10 +246,10 @@ module.exports = (opts) -> ### sort_entries = (types) -> - W.map types, (t) => + W.map types, (t) -> if t.sort - # Unfortunately, in order to sort promises we have to resolve them first. - W.all(t.content).then (data) => + # In order to sort promises we have to resolve them first. + W.all(t.content).then (data) -> t.content = data.sort(t.sort) W.resolve(t) diff --git a/readme.md b/readme.md index 471eed4..119a110 100644 --- a/readme.md +++ b/readme.md @@ -30,6 +30,9 @@ module.exports = contentful access_token: 'YOUR_ACCESS_TOKEN' space_id: 'xxxxxx' + locale: 'tlh' + locales_prefix: + tlh: 'klingon_' content_types: blog_posts: id: 'xxxxxx' @@ -104,6 +107,29 @@ Required. The space ID containing the content you wish to retrieve. Optional. (Boolean) Allows you use the Contentful Preview API. Also able to be accessed by setting the environment variable `CONTENTFUL_ENV` to `"develop"` (preview api) or `"production"` (default cdn). +#### locales +Locales allow you to request your content in a different language, `tlh` is Klingon. + +##### Global locale +Optional. (String or Array) Defines locale for all content_types. +String: `'tlh'` +Array: `['en-es', 'tlh']` +Wildcard: `'*'` - grabs all locales from contentful + +##### content_type specific locale +Optional. (String) Define content_types locale, will override global locale. Add `locale: 'tlh'` to any of your content types and you'll retrieve that post in the specific locale. + +#### locales_prefix +Optional. (Object) Defines the prefix given to a group of locales. + +``` +locales_prefix: + 'tlh': 'klingon_' + 'en-es': 'spanish_' +``` + +Lets say you have 3 global locales defined, `['tlh', 'en-us', 'en-es']`, and above is our defined locales_prefix. Since we did not declare `'en-us'` you can access it with `contentful.en_us_blog_posts`. Similarly you can access each preix according to what you've set in the object above. `'tlh'` would be accessible by `contentful.klingon_blog_posts` and `en-es` by `contentful.spanish_blog_posts`. + #### content_types An object whose key-value pairs correspond to a Contentful Content Types. Each diff --git a/test/fixtures/locale_global/about.jade b/test/fixtures/locale_global/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/locale_global/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/locale_global/app.coffee b/test/fixtures/locale_global/app.coffee new file mode 100644 index 0000000..b26ecce --- /dev/null +++ b/test/fixtures/locale_global/app.coffee @@ -0,0 +1,17 @@ +contentful = require '../../..' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['en-US'] + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + locale: 'en-es' + } + ] + ) + ] diff --git a/test/fixtures/locale_global/index.jade b/test/fixtures/locale_global/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locale_global/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locale_global/package.json b/test/fixtures/locale_global/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locale_global/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locale_multi/app.coffee b/test/fixtures/locale_multi/app.coffee new file mode 100644 index 0000000..647c89c --- /dev/null +++ b/test/fixtures/locale_multi/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../..' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['en-es', 'tlh'] + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locale_multi/index.jade b/test/fixtures/locale_multi/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locale_multi/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locale_multi/package.json b/test/fixtures/locale_multi/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locale_multi/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locale_prefix/app.coffee b/test/fixtures/locale_prefix/app.coffee new file mode 100644 index 0000000..45224d2 --- /dev/null +++ b/test/fixtures/locale_prefix/app.coffee @@ -0,0 +1,19 @@ +contentful = require '../../..' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['en-es', 'tlh'] + locales_prefix: { + 'tlh': 'klingon_' + } + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locale_prefix/klingon.jade b/test/fixtures/locale_prefix/klingon.jade new file mode 100644 index 0000000..9c7fe36 --- /dev/null +++ b/test/fixtures/locale_prefix/klingon.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.klingon_blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locale_prefix/package.json b/test/fixtures/locale_prefix/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locale_prefix/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locale_prefix/spanish.jade b/test/fixtures/locale_prefix/spanish.jade new file mode 100644 index 0000000..620ac70 --- /dev/null +++ b/test/fixtures/locale_prefix/spanish.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.en_es_blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locale_scope/about.jade b/test/fixtures/locale_scope/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/locale_scope/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/locale_scope/app.coffee b/test/fixtures/locale_scope/app.coffee new file mode 100644 index 0000000..3710671 --- /dev/null +++ b/test/fixtures/locale_scope/app.coffee @@ -0,0 +1,17 @@ +contentful = require '../../..' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['tlh'] + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + locale: 'en-es' + } + ] + ) + ] diff --git a/test/fixtures/locale_scope/index.jade b/test/fixtures/locale_scope/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locale_scope/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locale_scope/package.json b/test/fixtures/locale_scope/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locale_scope/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locale_setup/about.jade b/test/fixtures/locale_setup/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/locale_setup/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/locale_setup/app.coffee b/test/fixtures/locale_setup/app.coffee new file mode 100644 index 0000000..572318d --- /dev/null +++ b/test/fixtures/locale_setup/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../..' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: '*' + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locale_setup/index.jade b/test/fixtures/locale_setup/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locale_setup/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locale_setup/package.json b/test/fixtures/locale_setup/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locale_setup/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/fixtures/locale_single/about.jade b/test/fixtures/locale_single/about.jade new file mode 100644 index 0000000..8240f0e --- /dev/null +++ b/test/fixtures/locale_single/about.jade @@ -0,0 +1 @@ +h1 wow diff --git a/test/fixtures/locale_single/app.coffee b/test/fixtures/locale_single/app.coffee new file mode 100644 index 0000000..56e976f --- /dev/null +++ b/test/fixtures/locale_single/app.coffee @@ -0,0 +1,16 @@ +contentful = require '../../..' + +module.exports = + ignores: ["**/_*", "**/.DS_Store"] + extensions: [ + contentful( + access_token: 'YOUR_ACCESS_TOKEN' + space_id: 'aqzq2qya2jm4' + locale: ['tlh'] + content_types: [ + { + id: '6BYT1gNiIEyIw8Og8aQAO6' + } + ] + ) + ] diff --git a/test/fixtures/locale_single/index.jade b/test/fixtures/locale_single/index.jade new file mode 100644 index 0000000..4769500 --- /dev/null +++ b/test/fixtures/locale_single/index.jade @@ -0,0 +1,5 @@ +ul + - for p in contentful.blog_posts + li + h1= p.title + p= p.body diff --git a/test/fixtures/locale_single/package.json b/test/fixtures/locale_single/package.json new file mode 100644 index 0000000..2d0ae2c --- /dev/null +++ b/test/fixtures/locale_single/package.json @@ -0,0 +1,6 @@ +{ + "name": "test", + "dependencies": { + "jade": "*" + } +} diff --git a/test/test.coffee b/test/test.coffee index cc46f31..038f61d 100644 --- a/test/test.coffee +++ b/test/test.coffee @@ -19,10 +19,20 @@ mock_contentful = (opts = {}) -> entries: [ sys: sys: 'data' + locale: 'Default Locale' fields: title: 'Default Title' body: 'Default Body' ] + space: + sys: + type: "Space", + id: "cfexampleapi" + name: "Contentful Example API", + locales: [ + {code: "en-US", name: "English"}, + {code: "tlh", name: "Klingon"} + ] content_type: name: 'Blog Post' displayField: 'title' @@ -30,7 +40,14 @@ mock_contentful = (opts = {}) -> mockery.registerMock 'contentful', createClient: -> contentType: -> W.resolve(opts.content_type) - entries: -> W.resolve(opts.entries) + space: -> W.resolve(opts.space) + entries: (req) -> + if _.isUndefined req.locale + W.resolve(opts.entries) + else + W.resolve opts.entries.filter (entry) -> + return entry.sys.locale is req.locale + unmock_contentful = -> mockery.deregisterAll() @@ -243,7 +260,7 @@ describe 'single entry views', -> after -> unmock_contentful() - it 'should not have first entry\'s content in second entries single view', -> + it 'should not have first entry\'s content in 2nd entries single view', -> p = path.join(@public, "blog_posts/#{S(@title_2).slugify().s}.html") h.file.contains(p, @body).should.not.be.true @@ -312,3 +329,242 @@ describe 'single entry views', -> h.file.contains(p, "#{@img_path}?w=100&h=100").should.be.true after -> unmock_contentful() + +describe 'locale', -> + describe 'setup', -> + before (done) -> + @title = ['Throw Some Ds', '\'op Ds chuH', 'arrojar algo de Ds\''] + @body = [ + 'Rich Boy selling crack', + 'mIp loDHom ngev pe\'vIl vaj pumDI\' qoghlIj' + 'Niño rico venta de crack' + ] + mock_contentful( + entries: [ + { + fields: {title: @title[0], body: @body[0]} + sys: + locale: 'en-US' + } + { + fields: {title: @title[1], body: @body[1]}, + sys: + locale: 'tlh' + } + { + fields: {title: @title[2], body: @body[2]}, + sys: + locale: 'en-es' + } + ], + space: + locales: [ + { code: 'en-US', name: 'English' }, + { code: 'tlh', name: 'Klingon' }, + { code: 'en-es', name: 'Spanish' } + ] + ) + compile_fixture.call(@, 'locale_setup').then(-> done()).catch(done) + + it 'should fetch all locales from * wildcard', -> + p = path.join @public, 'index.html' + for title, i in @title + h.file.contains p, title + .should.be.true + h.file.contains p, @body[i] + .should.be.true + + after -> unmock_contentful() + + describe 'global', -> + describe 'single locale', -> + before (done) -> + @title = ['Throw Some Ds', '\'op Ds chuH', 'arrojar algo de Ds\''] + @body = [ + 'Rich Boy selling crack', + 'mIp loDHom ngev pe\'vIl vaj pumDI\' qoghlIj' + 'Niño rico venta de crack' + ] + mock_contentful( + entries: [ + { + fields: {title: @title[0], body: @body[0]} + sys: + locale: 'en-US' + } + { + fields: {title: @title[1], body: @body[1]}, + sys: + locale: 'tlh' + } + { + fields: {title: @title[2], body: @body[2]}, + sys: + locale: 'en-es' + } + ], + space: + locales: [ + { code: 'en-US', name: 'English' }, + { code: 'tlh', name: 'Klingon' }, + { code: 'en-es', name: 'Spanish' } + ] + ) + compile_fixture.call(@, 'locale_single').then(-> done()).catch(done) + + it 'should render a single global', -> + p = path.join @public, 'index.html' + h.file.contains p, @title[0] + .should.be.false + h.file.contains p, @title[1] + .should.be.true + + after -> unmock_contentful() + + describe 'array of locales', -> + before (done) -> + @title = ['Throw Some Ds', '\'op Ds chuH', 'ajrrojar algo de Ds\''] + @body = [ + 'Rich Boy selling crack', + 'mIp loDHom ngev pe\'vIl vaj pumDI\' qoghlIj' + 'Niño rico venta de crack' + ] + mock_contentful( + entries: [ + { + fields: {title: @title[0], body: @body[0]} + sys: + locale: 'en-US' + } + { + fields: {title: @title[1], body: @body[1]}, + sys: + locale: 'tlh' + } + { + fields: {title: @title[2], body: @body[2]}, + sys: + locale: 'en-es' + } + ], + space: + locales: [ + { code: 'en-US', name: 'English' }, + { code: 'tlh', name: 'Klingon' }, + { code: 'en-es', name: 'Spanish' } + ] + ) + compile_fixture.call(@, 'locale_multi').then(-> done()).catch(done) + + + it 'should render an array of global locales', -> + p = path.join @public, 'index.html' + h.file.contains p, @title[1] + .should.be.true + h.file.contains p, @title[2] + .should.be.true + h.file.contains p, @title[0] + .should.be.false + + after -> unmock_contentful() + + describe 'scoped', -> + before (done) -> + @title = ['Throw Some Ds', '\'op Ds chuH', 'arrojar algo de Ds\''] + @body = [ + 'Rich Boy selling crack', + 'mIp loDHom ngev pe\'vIl vaj pumDI\' qoghlIj' + 'Niño rico venta de crack' + ] + mock_contentful( + entries: [ + { + fields: {title: @title[0], body: @body[0]} + sys: + locale: 'en-US' + } + { + fields: {title: @title[1], body: @body[1]}, + sys: + locale: 'tlh' + } + { + fields: {title: @title[2], body: @body[2]}, + sys: + locale: 'en-es' + } + ], + space: + locales: [ + { code: 'en-US', name: 'English' }, + { code: 'tlh', name: 'Klingon' }, + { code: 'en-es', name: 'Spanish' } + ] + ) + compile_fixture.call(@, 'locale_scope').then(-> done()).catch(done) + + it 'should render the content type locale, not the global', -> + p = path.join @public, 'index.html' + h.file.contains p, @title[2] + .should.be.true + h.file.contains p, @body[2] + .should.be.true + h.file.contains p, @title[1] + .should.be.false + h.file.contains p, @body[1] + .should.be.false + + after -> unmock_contentful() + + describe 'locales_prefix', -> + before (done) -> + @title = ['Throw Some Ds', '\'op Ds chuH', 'arrojar algo de Ds\''] + @body = [ + 'Rich Boy selling crack', + 'mIp loDHom ngev pe\'vIl vaj pumDI\' qoghlIj' + 'Niño rico venta de crack' + ] + mock_contentful( + entries: [ + { + fields: {title: @title[0], body: @body[0]} + sys: + locale: 'en-US' + } + { + fields: {title: @title[1], body: @body[1]}, + sys: + locale: 'tlh' + } + { + fields: {title: @title[2], body: @body[2]}, + sys: + locale: 'en-es' + } + ], + space: + locales: [ + { code: 'en-US', name: 'English' }, + { code: 'tlh', name: 'Klingon' }, + { code: 'en-es', name: 'Spanish' } + ] + ) + compile_fixture.call(@, 'locale_prefix').then(-> done()).catch(done) + + it 'should render using the correct template', -> + klingon = path.join @public, 'klingon.html' + spanish = path.join @public, 'spanish.html' + + h.file.contains klingon, @title[1] + .should.be.true + h.file.contains klingon, @body[1] + .should.be.true + h.file.contains klingon, @body[2] + .should.be.false + + h.file.contains spanish, @title[2] + .should.be.true + h.file.contains spanish, @body[2] + .should.be.true + h.file.contains spanish, @body[1] + .should.be.false