From 0bd4fbc7b8ad8cf0eb34c77e727fc22b41c2172b Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Fri, 4 Jun 2021 12:25:24 +0100 Subject: [PATCH 01/21] Upgrade packages --- package.json | 6 +++--- yarn.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 87cfa3d..ba9e093 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,11 @@ "build": "rm -rf lib && rollup -c" }, "dependencies": { - "camelcase": "^6.0.0", - "got": "^11.6.2" + "camelcase": "^6.2.0", + "got": "^11.8.2" }, "devDependencies": { "eslint-config-travisreynolds-node": "^1.2.0", - "rollup": "^2.26.11" + "rollup": "^2.50.6" } } diff --git a/yarn.lock b/yarn.lock index 89a1d51..a4e6f40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -109,10 +109,10 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@sindresorhus/is@^3.1.1": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-3.1.2.tgz#548650de521b344e3781fbdb0ece4aa6f729afb8" - integrity sha512-JiX9vxoKMmu8Y3Zr2RVathBL1Cdu4Nt4MuNWemt1Nc06A0RAin9c5FArkhGsyMBWfCu4zj+9b+GxtjAnE4qqLQ== +"@sindresorhus/is@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" + integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== "@szmarczak/http-timer@^4.0.5": version "4.0.5" @@ -292,10 +292,10 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camelcase@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" - integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== +camelcase@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" + integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== chalk@^2.0.0: version "2.4.2" @@ -729,10 +729,10 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fsevents@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: version "1.1.1" @@ -782,12 +782,12 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -got@^11.6.2: - version "11.6.2" - resolved "https://registry.yarnpkg.com/got/-/got-11.6.2.tgz#79d7bb8c11df212b97f25565407a1f4ae73210ec" - integrity sha512-/21qgUePCeus29Jk7MEti8cgQUNXFSWfIevNIk4H7u1wmXNDrGPKPY6YsPY+o9CIT/a2DjCjRz0x1nM9FtS2/A== +got@^11.8.2: + version "11.8.2" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.2.tgz#7abb3959ea28c31f3576f1576c1effce23f33599" + integrity sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ== dependencies: - "@sindresorhus/is" "^3.1.1" + "@sindresorhus/is" "^4.0.0" "@szmarczak/http-timer" "^4.0.5" "@types/cacheable-request" "^6.0.1" "@types/responselike" "^1.0.0" @@ -1295,12 +1295,12 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rollup@^2.26.11: - version "2.26.11" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.26.11.tgz#4fc31de9c7b83d50916fc8395f8c3d24730cdaae" - integrity sha512-xyfxxhsE6hW57xhfL1I+ixH8l2bdoIMaAecdQiWF3N7IgJEMu99JG+daBiSZQjnBpzFxa0/xZm+3pbCdAQehHw== +rollup@^2.50.6: + version "2.50.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.50.6.tgz#24e2211caf9031081656e98a5e5e94d3b5e786e2" + integrity sha512-6c5CJPLVgo0iNaZWWliNu1Kl43tjP9LZcp6D/tkf2eLH2a9/WeHxg9vfTFl8QV/2SOyaJX37CEm9XuGM0rviUg== optionalDependencies: - fsevents "~2.1.2" + fsevents "~2.3.1" "semver@2 || 3 || 4 || 5": version "5.7.1" From 800d7d8153219efd42b997d27485dd0ee47bcd6d Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 13:59:57 +0100 Subject: [PATCH 02/21] Update min Node version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba9e093..b660f0d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.2.6", "main": "lib/index.js", "engines": { - "node": ">=10.x" + "node": ">=12.x" }, "author": { "email": "travis@travisreynolds.dev", From 194f2108b0c4dd191d915dae269db545ae8d58a2 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 14:00:18 +0100 Subject: [PATCH 03/21] Update dependencies --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index b660f0d..fd41b54 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,6 @@ }, "devDependencies": { "eslint-config-travisreynolds-node": "^1.2.0", - "rollup": "^2.50.6" + "rollup": "^2.55.1" } } diff --git a/yarn.lock b/yarn.lock index a4e6f40..9905d35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -729,7 +729,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@~2.3.1: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -1295,12 +1295,12 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" -rollup@^2.50.6: - version "2.50.6" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.50.6.tgz#24e2211caf9031081656e98a5e5e94d3b5e786e2" - integrity sha512-6c5CJPLVgo0iNaZWWliNu1Kl43tjP9LZcp6D/tkf2eLH2a9/WeHxg9vfTFl8QV/2SOyaJX37CEm9XuGM0rviUg== +rollup@^2.55.1: + version "2.55.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.55.1.tgz#66a444648e2fb603d8e329e77a61c608a6510fda" + integrity sha512-1P9w5fpb6b4qroePh8vHKGIvPNxwoCQhjJpIqfZGHLKpZ0xcU2/XBmFxFbc9697/6bmHpmFTLk5R1dAQhFSo0g== optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" "semver@2 || 3 || 4 || 5": version "5.7.1" From 1836d28745f9d0975ad21bb00e8f2eac7651cc76 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:11:18 +0100 Subject: [PATCH 04/21] Add lint command --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fd41b54..3ec060d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ ], "scripts": { "develop": "rollup -c --watch", - "build": "rm -rf lib && rollup -c" + "build": "rm -rf lib && rollup -c", + "lint": "eslint src --fix" }, "dependencies": { "camelcase": "^6.2.0", From 04659a178e5480bf0b59dedefcef5d6ba9740204 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:19:23 +0100 Subject: [PATCH 05/21] Update to add headers to client --- src/client.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index 49ac073..f3b500b 100644 --- a/src/client.js +++ b/src/client.js @@ -19,10 +19,11 @@ export const createClient = ({ storeUrl, storefrontToken, timeout }) => got.exte * Get all paginated data from a query. Will execute multiple requests as * needed. */ -export const queryAll = async (client, query, variables) => { +export const queryAll = async (client, query, variables, headers) => { const items = client.paginate.each('graphql.json', { method: 'POST', json: { query, variables }, + headers: headers || {}, pagination: { backoff: 1000, transform: ({ body: { data, errors } }) => { @@ -54,7 +55,7 @@ export const queryAll = async (client, query, variables) => { } // Currently setup for Collection.products field, but can extend this method in future, if needed - if (!node.products.pageInfo.hasNextPage) { + if (!node.products || !node.products.pageInfo.hasNextPage) { allNodes.push(node) continue } From 608934402a06d921577a69066216a97d1638bab4 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:19:40 +0100 Subject: [PATCH 06/21] Update to add translation queries --- src/queries.js | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/queries.js b/src/queries.js index b92d04d..a7fd4b5 100644 --- a/src/queries.js +++ b/src/queries.js @@ -54,6 +54,26 @@ export const ARTICLES_QUERY = ` } } ` +export const ARTICLES_TRANSLATIONS_QUERY = ` + query Articles ($first: Int!, $after: String) { + data: articles (first: $first, after: $after) { + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + title + content + contentHtml + excerpt + excerptHtml + } + } + } + } +` export const BLOGS_QUERY = ` query Blogs ($first: Int!, $after: String) { @@ -76,6 +96,22 @@ export const BLOGS_QUERY = ` } } ` +export const BLOGS_TRANSLATIONS_QUERY = ` + query Blogs ($first: Int!, $after: String) { + data: blogs(first: $first, after: $after) { + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + title + } + } + } + } +` export const COLLECTIONS_QUERY = ` query Collections ($first: Int!, $after: String) { @@ -114,6 +150,25 @@ export const COLLECTIONS_QUERY = ` } } ` +export const COLLECTIONS_TRANSLATIONS_QUERY = ` + query Collections ($first: Int!, $after: String) { + data: collections (first: $first, after: $after) { + pageInfo { + hasNextPage + } + edges { + typeName: __typename + cursor + node { + id + title + description + descriptionHtml + } + } + } + } +` export const COLLECTION_QUERY = `query SingleCollection ($handle: String!, $first: Int!, $after: String) { collection: collectionByHandle (handle: $handle) { @@ -250,6 +305,37 @@ export const PRODUCTS_QUERY = ` } ` +export const PRODUCTS_TRANSLATIONS_QUERY = ` + query Products ($first: Int!, $after: String) { + data: products (first: $first, after: $after) { + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + title + description + descriptionHtml + variants(first: 250) { + edges { + node { + id + title + selectedOptions { + name + value + } + } + } + } + } + } + } + } +` + export const SHOP_QUERY = ` query Shop { shop { @@ -330,3 +416,21 @@ export const PAGES_QUERY = ` } } ` + +export const PAGES__TRANSLATIONS_QUERY = ` + query Pages ($first: Int!) { + data: pages (first: $first) { + pageInfo { + hasNextPage + } + edges { + cursor + node { + id + title + body + } + } + } + } +` From f13ecdda3e91475eb272478d3255be2d025b1bff Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:20:14 +0100 Subject: [PATCH 07/21] Update to add translateable fields --- src/schema.js | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/schema.js b/src/schema.js index 54a21dd..a38195f 100644 --- a/src/schema.js +++ b/src/schema.js @@ -1,4 +1,4 @@ -export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { TYPENAMES }) => { +export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { TYPENAMES, hasLocales, createShopifyId }) => { addSchemaTypes([ schema.createEnumType({ name: `${TYPENAMES.IMAGE}CropMode`, @@ -70,4 +70,38 @@ export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { T } } }) + + if (hasLocales) { + const translateableTypes = [ + [TYPENAMES.PRODUCT, TYPENAMES.PRODUCT_TRANSLATION, ['title', 'description', 'descriptionHtml']], + [TYPENAMES.PRODUCT_VARIANT, TYPENAMES.PRODUCT_VARIANT_TRANSLATION, ['title', 'selectedOptions']], + [TYPENAMES.COLLECTION, TYPENAMES.COLLECTION_TRANSLATION, ['title', 'description', 'descriptionHtml']], + [TYPENAMES.ARTICLE, TYPENAMES.ARTICLE_TRANSLATION, ['title', 'content', 'contentHtml', 'excerpt', 'excerptHtml']], + [TYPENAMES.BLOG, TYPENAMES.BLOG_TRANSLATION, ['title']], + [TYPENAMES.PAGE, TYPENAMES.PAGE_TRANSLATION, ['title', 'body']] + ] + + const resolvers = translateableTypes.map(([typeName, translationTypeName, fields]) => { + const resolvers = fields.map(field => { + return [field, { + args: { + locale: 'String' + }, + resolve: (parent, { locale }, ctx) => { + if (!locale) return Reflect.get(parent, field) + const translationId = createShopifyId(parent.id, `Locale${locale.toUpperCase()}`) + const translationsStore = ctx.store.getCollection(translationTypeName) + + const translation = translationsStore.getNode(translationId) + if (!translation) return Reflect.get(parent, field) + return Reflect.get(translation, field) + } + }] + }) + + return [typeName, Object.fromEntries(resolvers)] + }) + + addSchemaResolvers(Object.fromEntries(resolvers)) + } } From 258cb94e8c0afcd04cdece57ff5d3418cc45b6ee Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:21:27 +0100 Subject: [PATCH 08/21] Functions to add locales & resources to store --- src/index.js | 146 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 08968bc..51a563a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import camelCase from 'camelcase' import { createClient, queryAll } from './client' import { createSchema } from './schema' -import { COLLECTIONS_QUERY, PRODUCTS_QUERY, PRODUCT_TYPES_QUERY, ARTICLES_QUERY, BLOGS_QUERY, PAGES_QUERY, PRODUCT_TAGS_QUERY } from './queries' +import { COLLECTIONS_QUERY, PRODUCTS_QUERY, PRODUCT_TYPES_QUERY, ARTICLES_QUERY, BLOGS_QUERY, PAGES_QUERY, PRODUCT_TAGS_QUERY, PRODUCTS_TRANSLATIONS_QUERY, COLLECTIONS_TRANSLATIONS_QUERY, BLOGS_TRANSLATIONS_QUERY, ARTICLES_TRANSLATIONS_QUERY, PAGES__TRANSLATIONS_QUERY } from './queries' class ShopifySource { static defaultOptions () { @@ -12,7 +12,8 @@ class ShopifySource { typeName: 'Shopify', types: [], perPage: 100, - timeout: 60000 + timeout: 60000, + locales: [] } } @@ -22,15 +23,27 @@ class ShopifySource { if (!options.storeUrl && !options.storeName) throw new Error('Missing store name or url.') if (!options.storefrontToken) throw new Error('Missing storefront access token.') if (options.storeName) this.options.storeUrl = `https://${options.storeName}.myshopify.com` + if (options.locales) { + if (!Array.isArray(options.locales)) throw new Error('The locales option must be an array of strings.') + if (!options.locales.length) return + this.options.hasLocales = true + this.options.locales = options.locales.map(l => l.toLowerCase().trim()) + } // Node Types this.TYPENAMES = { ARTICLE: this.createTypeName('Article'), + ARTICLE_TRANSLATION: this.createTypeName('ArticleTranslation'), BLOG: this.createTypeName('Blog'), + BLOG_TRANSLATION: this.createTypeName('BlogTranslation'), COLLECTION: this.createTypeName('Collection'), + COLLECTION_TRANSLATION: this.createTypeName('CollectionTranslation'), PRODUCT: this.createTypeName('Product'), + PRODUCT_TRANSLATION: this.createTypeName('ProductTranslation'), PRODUCT_VARIANT: this.createTypeName('ProductVariant'), + PRODUCT_VARIANT_TRANSLATION: this.createTypeName('ProductVariantTranslation'), PAGE: this.createTypeName('Page'), + PAGE_TRANSLATION: this.createTypeName('PageTranslation'), PRODUCT_TYPE: this.createTypeName('ProductType'), PRODUCT_TAG: this.createTypeName('ProductTag'), IMAGE: 'ShopifyImage', @@ -44,7 +57,7 @@ class ShopifySource { // Create custom schema type for ShopifyImage api.loadSource(actions => { - createSchema(actions, { TYPENAMES: this.TYPENAMES }) + createSchema(actions, { TYPENAMES: this.TYPENAMES, hasLocales: options.hasLocales, createShopifyId: this.createShopifyId }) }) // Load data into store @@ -55,10 +68,15 @@ class ShopifySource { await this.getProductTypes(actions) await this.getProductTags(actions) await this.getCollections(actions) + await this.getCollectionsTranslations(actions) await this.getProducts(actions) + await this.getProductTranslations(actions) await this.getBlogs(actions) + await this.getBlogsTranslations(actions) await this.getArticles(actions) + await this.getArticlesTranslations(actions) await this.getPages(actions) + await this.getPagesTranslations(actions) }) } @@ -111,7 +129,29 @@ class ShopifySource { } } - async getProducts (actions) { + async getCollectionsTranslations (actions) { + if (!this.typesToInclude.includes(this.TYPENAMES.COLLECTION_TRANSLATION) || !this.options.hasLocales) return + + const collectionsTranslationsStore = actions.addCollection({ typeName: this.TYPENAMES.COLLECTION_TRANSLATION }) + + for (const locale of this.options.locales) { + const collectionsTranslations = await queryAll(this.shopify, COLLECTIONS_TRANSLATIONS_QUERY, { first: this.options.perPage }, { 'Accept-Language': locale }) + + for (const collection of collectionsTranslations) { + const originalId = collection.id + const id = this.createShopifyId(collection.id, `Locale${locale.toUpperCase()}`) + + collectionsTranslationsStore.addNode({ + ...collection, + id, + locale, + originalId + }) + } + } + } + + async getProducts (actions, locale) { if (!this.typesToInclude.includes(this.TYPENAMES.PRODUCT)) return const productStore = actions.addCollection({ typeName: this.TYPENAMES.PRODUCT }) @@ -170,6 +210,38 @@ class ShopifySource { return { minVariantPrice: actions.createReference(minVariantPrice), maxVariantPrice: actions.createReference(maxVariantPrice) } } + async getProductTranslations (actions) { + if (!this.typesToInclude.includes(this.TYPENAMES.PRODUCT_TRANSLATION) || !this.options.hasLocales) return + + const productTranslationsStore = actions.addCollection({ typeName: this.TYPENAMES.PRODUCT_TRANSLATION }) + const productVariantTranslationsStore = actions.addCollection({ typeName: this.TYPENAMES.PRODUCT_VARIANT_TRANSLATION }) + + for (const locale of this.options.locales) { + const productsTranslations = await queryAll(this.shopify, PRODUCTS_TRANSLATIONS_QUERY, { first: this.options.perPage }, { 'Accept-Language': locale }) + + for (const product of productsTranslations) { + const originalId = product.id + const id = this.createShopifyId(product.id, `Locale${locale.toUpperCase()}`) + + const variants = product.variants.edges.map(({ node: variant }) => { + const originalId = variant.id + const id = this.createShopifyId(variant.id, `Locale${locale.toUpperCase()}`) + + const variantNode = productVariantTranslationsStore.addNode({ ...variant, id, originalId, locale }) + return actions.createReference(variantNode) + }) + + productTranslationsStore.addNode({ + ...product, + id, + locale, + originalId, + variants + }) + } + } + } + async getBlogs (actions) { if (!this.typesToInclude.includes(this.TYPENAMES.BLOG)) return @@ -182,6 +254,28 @@ class ShopifySource { } } + async getBlogsTranslations (actions) { + if (!this.typesToInclude.includes(this.TYPENAMES.BLOG_TRANSLATION) || !this.options.hasLocales) return + + const blogsTranslationsStore = actions.addCollection({ typeName: this.TYPENAMES.BLOG_TRANSLATION }) + + for (const locale of this.options.locales) { + const blogsTranslations = await queryAll(this.shopify, BLOGS_TRANSLATIONS_QUERY, { first: this.options.perPage }, { 'Accept-Language': locale }) + + for (const blog of blogsTranslations) { + const originalId = blog.id + const id = this.createShopifyId(blog.id, `Locale${locale.toUpperCase()}`) + + blogsTranslationsStore.addNode({ + ...blog, + id, + locale, + originalId + }) + } + } + } + async getArticles (actions) { if (!this.typesToInclude.includes(this.TYPENAMES.ARTICLE)) return @@ -204,6 +298,28 @@ class ShopifySource { } } + async getArticlesTranslations (actions) { + if (!this.typesToInclude.includes(this.TYPENAMES.ARTICLE_TRANSLATION) || !this.options.hasLocales) return + + const articlesTranslationsStore = actions.addCollection({ typeName: this.TYPENAMES.ARTICLE_TRANSLATION }) + + for (const locale of this.options.locales) { + const articlesTranslations = await queryAll(this.shopify, ARTICLES_TRANSLATIONS_QUERY, { first: this.options.perPage }, { 'Accept-Language': locale }) + + for (const article of articlesTranslations) { + const originalId = article.id + const id = this.createShopifyId(article.id, `Locale${locale.toUpperCase()}`) + + articlesTranslationsStore.addNode({ + ...article, + id, + locale, + originalId + }) + } + } + } + async getPages (actions) { if (!this.typesToInclude.includes(this.TYPENAMES.PAGE)) return @@ -216,6 +332,28 @@ class ShopifySource { } } + async getPagesTranslations (actions) { + if (!this.typesToInclude.includes(this.TYPENAMES.PAGE_TRANSLATION) || !this.options.hasLocales) return + + const pagesTranslationsStore = actions.addCollection({ typeName: this.TYPENAMES.PAGE_TRANSLATION }) + + for (const locale of this.options.locales) { + const pagesTranslations = await queryAll(this.shopify, PAGES__TRANSLATIONS_QUERY, { first: this.options.perPage }, { 'Accept-Language': locale }) + + for (const page of pagesTranslations) { + const originalId = page.id + const id = this.createShopifyId(page.id, `Locale${locale.toUpperCase()}`) + + pagesTranslationsStore.addNode({ + ...page, + id, + locale, + originalId + }) + } + } + } + createTypeName (name) { let typeName = this.options.typeName // If typeName is blank, we need to add a prefix to these types anyway, as on their own they conflict with internal Gridsome types. From c2e112e8efecc7f5a1611a6010f24c69b674e68a Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:26:12 +0100 Subject: [PATCH 09/21] v0.3.0-beta.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ec060d..3610d4a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gridsome-source-shopify", "description": "Shopify source plugin for Gridsome", - "version": "0.2.6", + "version": "0.3.0-beta.1", "main": "lib/index.js", "engines": { "node": ">=12.x" From f9fbaa72966821562a7dad3963d723fb5d89cc37 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:34:50 +0100 Subject: [PATCH 10/21] Update dpelling mistake --- src/schema.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/schema.js b/src/schema.js index a38195f..fd59ce8 100644 --- a/src/schema.js +++ b/src/schema.js @@ -72,7 +72,7 @@ export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { T }) if (hasLocales) { - const translateableTypes = [ + const translatableTypes = [ [TYPENAMES.PRODUCT, TYPENAMES.PRODUCT_TRANSLATION, ['title', 'description', 'descriptionHtml']], [TYPENAMES.PRODUCT_VARIANT, TYPENAMES.PRODUCT_VARIANT_TRANSLATION, ['title', 'selectedOptions']], [TYPENAMES.COLLECTION, TYPENAMES.COLLECTION_TRANSLATION, ['title', 'description', 'descriptionHtml']], @@ -81,7 +81,7 @@ export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { T [TYPENAMES.PAGE, TYPENAMES.PAGE_TRANSLATION, ['title', 'body']] ] - const resolvers = translateableTypes.map(([typeName, translationTypeName, fields]) => { + const resolvers = translatableTypes.map(([typeName, translationTypeName, fields]) => { const resolvers = fields.map(field => { return [field, { args: { From 7eaa84c61584c6d8c3a4584d69a059662110d416 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 16:44:18 +0100 Subject: [PATCH 11/21] Update license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 35a8e5b..9826a80 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Travis Reynolds +Copyright (c) 2021 Travis Reynolds Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 6873f0ae9adcd486b3fc043b3d499680bf36c5b5 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 17:19:14 +0100 Subject: [PATCH 12/21] Update readme --- README.md | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/README.md b/README.md index e14938d..a2e1280 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This plugin supports the Storefront API's [`transformedSrc` image field](#transf 3. [Routes & Templates](#routes--templates) 4. [Page Query](#page-query) 5. [Metafields](#metafields) +5. [Translations](#translations) 6. [Additional Resolvers](#additional-resolvers) 7. [Helpful Snippets](#helpful-snippets) @@ -112,6 +113,187 @@ Now this product will be available at `this.$page.shopifyProduct`: To make metafields available to query in the Storefront API, you should follow this guide: [Retrieve metafields with the Storefront API](https://shopify.dev/tutorials/retrieve-metafields-with-storefront-api). Then metafields will be available in your product query. +## Translations + +To fetch translations for relevant types, you need to an array of locales to the plugin config. This plugin will fetch the default Shopify locale by default, so this array should only include extra locales you want to fetch: +```js +config: { + // ... + locales: ['es', 'fr'] +} +``` + +These translations are then available a couple of different ways, depending on how your sites internationalization is setup. + +### Translatable Fields + +When the locales config has been added, a `locale` argument is added to relevant fields. This can then be used to get a specific translation for that field - it fits well with when you have a site that uses a locale URL prefix, so you have different pages for each locale version. For example: + +`gridsome.server.js` +```js +module.exports = api => { + api.createPages(async ({ graphql, createPage }) => { + const { data } = await graphql(` + allShopifyProduct { + edges { + node { + id + handle + } + } + } + `) + + const locales = ['en', 'es', 'fr'] + + for (const { node: product } of data.allShopifyProduct.edges) { + for (const locale of locales) { + createPage({ + path: `/${locale}/products/${product.handle}`, + component: './src/templates/Product.vue', + context: { + id: product.id, + locale + } + }) + } + } + }) +} +``` +`Product.vue` +```vue + + + + + +query Product ($id: ID!, $locale: String!) { + shopifyProduct (id: $id) { + id + title(locale: $locale) + descriptionHtml(locale: $locale) + variants { + id + title(locale: $locale) + price { + amount + } + selectedOptions (locale: $locale) { + name + value + } + } + } +} + +``` + +#### Translation Collections + +A translations collection is added for each relevant type, each with an array of nodes containing the translatable fields for that type. You can add filters to these queries to get a specific locale if needed - for example: + +```graphql +allShopifyProductTranslations (filter: { locale: {_eq: "es" }}) { + edges { + node { + id + originalId + title + description + variant { + title + } + } + } +} +``` +> Note: As these collections contain multiple copies of a node (for each translation), the ID is changed to keep it unique. The original Shopify ID can be found under `originalId`. + +This could be used when you have a site setup so there is one page that includes every translation, and a select input is used to switch between locales: + +```vue + + + + + +query Product ($id: ID!) { + shopifyProduct (id: $id) { + id + title + descriptionHtml + variants { + id + title + price { + amount + } + } + } + allShopifyProductTranslation (filter: { id: { eq: $id }}) { + edges { + node { + id + title + locale + descriptionHtml + variants { + title + selectedOptions { + name + value + } + } + } + } + } +} + +``` + ## Additional Resolvers This plugin adds a couple of custom resolvers to help with image sizing, and currency formatting. From 667c25fd3814784b650dbdf4ef03d4b470146594 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 17:20:19 +0100 Subject: [PATCH 13/21] Update to replace config with options --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2e1280..dcdd75d 100644 --- a/README.md +++ b/README.md @@ -115,9 +115,9 @@ Then metafields will be available in your product query. ## Translations -To fetch translations for relevant types, you need to an array of locales to the plugin config. This plugin will fetch the default Shopify locale by default, so this array should only include extra locales you want to fetch: +To fetch translations for relevant types, you need to an array of locales to the plugin options. This plugin will fetch the default Shopify locale by default, so this array should only include extra locales you want to fetch: ```js -config: { +options: { // ... locales: ['es', 'fr'] } From 4e29c9a54a42147e56721a0de2b6ed7f2d475100 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 17:20:54 +0100 Subject: [PATCH 14/21] Remove unecessary line. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index dcdd75d..ab86c97 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,6 @@ Now this product will be available at `this.$page.shopifyProduct`: ## Metafields To make metafields available to query in the Storefront API, you should follow this guide: [Retrieve metafields with the Storefront API](https://shopify.dev/tutorials/retrieve-metafields-with-storefront-api). -Then metafields will be available in your product query. ## Translations From 48c97e34f5b090479d620156c8ac2c2a99ad7e5a Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 17:21:58 +0100 Subject: [PATCH 15/21] Add example locale arg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab86c97..85a99a4 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ These translations are then available a couple of different ways, depending on h ### Translatable Fields -When the locales config has been added, a `locale` argument is added to relevant fields. This can then be used to get a specific translation for that field - it fits well with when you have a site that uses a locale URL prefix, so you have different pages for each locale version. For example: +When the locales config has been added, a `locale` argument is added to relevant fields: `title(locale: "es")`. This can then be used to get a specific translation for that field - it fits well with when you have a site that uses a locale URL prefix, so you have different pages for each locale version. For example: `gridsome.server.js` ```js From e2e9e304b51f20bf9ce6992a5499eda4ceb02798 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Tue, 3 Aug 2021 17:23:34 +0100 Subject: [PATCH 16/21] Update to keep spelling the same --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85a99a4..a5deb25 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ query Product ($id: ID!, $locale: String!) { ``` -#### Translation Collections +#### Translations Collections A translations collection is added for each relevant type, each with an array of nodes containing the translatable fields for that type. You can add filters to these queries to get a specific locale if needed - for example: From 68602e3b17b7c14ec5972a3ec3199f593c0e471f Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Sat, 7 Aug 2021 11:16:04 +0100 Subject: [PATCH 17/21] Add schema type --- src/schema.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/schema.js b/src/schema.js index fd59ce8..1cc0df4 100644 --- a/src/schema.js +++ b/src/schema.js @@ -84,6 +84,7 @@ export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { T const resolvers = translatableTypes.map(([typeName, translationTypeName, fields]) => { const resolvers = fields.map(field => { return [field, { + type: 'String', args: { locale: 'String' }, From a59d65d2b0ec55eb4f575b8391a87dfe63088b72 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Sat, 7 Aug 2021 11:29:42 +0100 Subject: [PATCH 18/21] Add fallback type field for resolvers --- src/schema.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/schema.js b/src/schema.js index 1cc0df4..5d9fd17 100644 --- a/src/schema.js +++ b/src/schema.js @@ -72,6 +72,13 @@ export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { T }) if (hasLocales) { + addSchemaTypes(` + type ${TYPENAMES.PRODUCT_VARIANT}_SelectedOptions @infer { + name: String + value: String + } + `) + const translatableTypes = [ [TYPENAMES.PRODUCT, TYPENAMES.PRODUCT_TRANSLATION, ['title', 'description', 'descriptionHtml']], [TYPENAMES.PRODUCT_VARIANT, TYPENAMES.PRODUCT_VARIANT_TRANSLATION, ['title', 'selectedOptions']], @@ -84,7 +91,7 @@ export const createSchema = ({ addSchemaTypes, schema, addSchemaResolvers }, { T const resolvers = translatableTypes.map(([typeName, translationTypeName, fields]) => { const resolvers = fields.map(field => { return [field, { - type: 'String', + type: field === 'selectedOptions' ? `[${TYPENAMES.PRODUCT_VARIANT}_SelectedOptions]` : 'String', args: { locale: 'String' }, From 5fb405b742cb512c6015520017293e455b067af6 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Sat, 7 Aug 2021 11:31:10 +0100 Subject: [PATCH 19/21] v0.3.0-beta.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3610d4a..d1b42f9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "gridsome-source-shopify", "description": "Shopify source plugin for Gridsome", - "version": "0.3.0-beta.1", + "version": "0.3.0-beta.2", "main": "lib/index.js", "engines": { "node": ">=12.x" From bb45a7baca85dc199fcf625070c5e1eb75bfecaa Mon Sep 17 00:00:00 2001 From: vappler490 <69162576+vappler490@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:45:34 +0200 Subject: [PATCH 20/21] fix graphql example query (#80) --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a5deb25..0b0d71c 100644 --- a/README.md +++ b/README.md @@ -133,11 +133,13 @@ When the locales config has been added, a `locale` argument is added to relevant module.exports = api => { api.createPages(async ({ graphql, createPage }) => { const { data } = await graphql(` - allShopifyProduct { - edges { - node { - id - handle + { + allShopifyProduct { + edges { + node { + id + handle + } } } } From afb839675b8c6f34c329c0b3e563d69c31c366e1 Mon Sep 17 00:00:00 2001 From: Travis Reynolds Date: Sat, 14 Aug 2021 19:31:44 +0100 Subject: [PATCH 21/21] Update to NOT use return -_- --- src/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 51a563a..19e927c 100644 --- a/src/index.js +++ b/src/index.js @@ -25,9 +25,10 @@ class ShopifySource { if (options.storeName) this.options.storeUrl = `https://${options.storeName}.myshopify.com` if (options.locales) { if (!Array.isArray(options.locales)) throw new Error('The locales option must be an array of strings.') - if (!options.locales.length) return - this.options.hasLocales = true - this.options.locales = options.locales.map(l => l.toLowerCase().trim()) + if (options.locales.length) { + this.options.hasLocales = true + this.options.locales = options.locales.map(l => l.toLowerCase().trim()) + } } // Node Types