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

feat(core): Add CSV import in multiple languages #1199

Merged
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/content/developer-guide/importing-product-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ To import custom fields with `list` set to `true`, the data should be separated
... ,tablet|pad|android
```

#### Importing data in multiple languages

If a field is translatable (i.e. of `localeString` type), you can use column names with an appended language code (e.g. `name:en`, `name:de`, `product:keywords:en`, `product:keywords:de`) to specify its value in multiple languages.

Use of language codes has to be consistent throughout the file. You don't have to translate every translatable field. If there are no translated columns for a field, the generic column's value will be used for all languages. But when you do translate columns, the set of languages for each of them needs to be the same. As an example, you cannot use `name:en` and `name:de`, but only provide `slug:en` (it's okay to use only a `slug` column though, in which case this slug will be used for both the English and the German version).

## Initial Data

As well as product data, other initialization data can be populated using the [`InitialData` object]({{< relref "initial-data" >}}). **This format is intentionally limited**; more advanced requirements (e.g. setting up ShippingMethods that use custom checkers & calculators) should be carried out via scripts which interact with the [Admin GraphQL API]({{< relref "/docs/graphql-api/admin" >}}).
Expand Down
106 changes: 106 additions & 0 deletions packages/core/e2e/__snapshots__/import.e2e-spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -371,3 +371,109 @@ Object {
],
}
`;

exports[`Import resolver imports products with multiple languages 1`] = `
Object {
"assets": Array [],
"customFields": Object {
"keywords": Array [
"paper, stretch",
],
"localName": "纸张拉伸器",
"owner": null,
"pageType": null,
},
"description": "一个用于拉伸纸张的伟大装置",
"featuredAsset": null,
"id": "T_5",
"name": "奇妙的纸张拉伸器",
"optionGroups": Array [
Object {
"code": "fantastic-paper-stretcher-size",
"id": "T_5",
"name": "size",
},
],
"slug": "奇妙的纸张拉伸器",
"variants": Array [
Object {
"assets": Array [],
"customFields": Object {
"weight": 243,
},
"featuredAsset": null,
"id": "T_11",
"name": "奇妙的纸张拉伸器 半英制",
"price": 4530,
"sku": "PPS12",
"stockMovements": Object {
"items": Array [
Object {
"id": "T_3",
"quantity": 10,
"type": "ADJUSTMENT",
},
],
},
"stockOnHand": 10,
"taxCategory": Object {
"id": "T_1",
"name": "Standard Tax",
},
"trackInventory": "FALSE",
},
Object {
"assets": Array [],
"customFields": Object {
"weight": 344,
},
"featuredAsset": null,
"id": "T_12",
"name": "奇妙的纸张拉伸器 四分之一英制",
"price": 3250,
"sku": "PPS14",
"stockMovements": Object {
"items": Array [
Object {
"id": "T_4",
"quantity": 10,
"type": "ADJUSTMENT",
},
],
},
"stockOnHand": 10,
"taxCategory": Object {
"id": "T_1",
"name": "Standard Tax",
},
"trackInventory": "FALSE",
},
Object {
"assets": Array [],
"customFields": Object {
"weight": 656,
},
"featuredAsset": null,
"id": "T_13",
"name": "奇妙的纸张拉伸器 全英制",
"price": 5950,
"sku": "PPSF",
"stockMovements": Object {
"items": Array [
Object {
"id": "T_5",
"quantity": 10,
"type": "ADJUSTMENT",
},
],
},
"stockOnHand": 10,
"taxCategory": Object {
"id": "T_1",
"name": "Standard Tax",
},
"trackInventory": "FALSE",
},
],
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name:en , name:zh_Hans , slug , description:en , description:zh_Hans , assets , facets:en , facets:zh_Hans , optionGroups , optionValues:en , optionValues:zh_Hans , sku , price , taxCategory , stockOnHand , trackInventory , variantAssets , variantFacets , product:keywords , product:localName:en , product:localName:zh_Hans , variant:weight
Fantastic Paper Stretcher , 奇妙的纸张拉伸器 , , A great device for stretching paper. , 一个用于拉伸纸张的伟大装置 , , make:KB|group:Accessory , 品牌:KB|类型:饰品 , size , Half Imperial , 半英制 , PPS12 , 45.3 , standard , 10 , false , , , "paper, stretch" , Paper Stretcher , 纸张拉伸器 , 243
, , , , , , , , , Quarter Imperial , 四分之一英制 , PPS14 , 32.5 , standard , 10 , false , , , , , , 344
, , , , , , , , , Full Imperial , 全英制 , PPSF , 59.5 , standard , 10 , false , , , , , , 656
153 changes: 153 additions & 0 deletions packages/core/e2e/import.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,157 @@ describe('Import resolver', () => {
expect(pencils.customFields.localName).toEqual('localGiotto');
expect(smock.customFields.localName).toEqual('localSmock');
}, 20000);

it('imports products with multiple languages', async () => {
// TODO: see test above
const timeout = process.env.CI ? 2000 : 1000;
await new Promise(resolve => {
setTimeout(resolve, timeout);
});

const csvFile = path.join(__dirname, 'fixtures', 'e2e-product-import-multi-languages.csv');
const result = await adminClient.fileUploadMutation({
mutation: gql`
mutation ImportProducts($csvFile: Upload!) {
importProducts(csvFile: $csvFile) {
imported
processed
errors
}
}
`,
filePaths: [csvFile],
mapVariables: () => ({ csvFile: null }),
});

expect(result.importProducts.errors).toEqual([]);
expect(result.importProducts.imported).toBe(1);
expect(result.importProducts.processed).toBe(1);

const productResult = await adminClient.query(
gql`
query GetProducts($options: ProductListOptions) {
products(options: $options) {
totalItems
items {
id
name
slug
description
featuredAsset {
id
name
preview
source
}
assets {
id
name
preview
source
}
optionGroups {
id
code
name
}
facetValues {
id
name
facet {
id
name
}
}
customFields {
pageType
owner {
id
}
keywords
localName
}
variants {
id
name
sku
price
taxCategory {
id
name
}
options {
id
code
name
}
assets {
id
name
preview
source
}
featuredAsset {
id
name
preview
source
}
facetValues {
id
code
name
facet {
id
name
}
}
stockOnHand
trackInventory
stockMovements {
items {
... on StockMovement {
id
type
quantity
}
}
}
customFields {
weight
}
}
}
}
}
`,
{
options: {},
},
{
languageCode: 'zh_Hans',
},
);

expect(productResult.products.totalItems).toBe(5);

const paperStretcher = productResult.products.items.find((p: any) => p.name === '奇妙的纸张拉伸器');

// Omit FacetValues & options due to variations in the ordering between different DB engines
expect(omit(paperStretcher, ['facetValues', 'options'], true)).toMatchSnapshot();

const byName = (e: { name: string }) => e.name;

expect(paperStretcher.facetValues.map(byName).sort()).toEqual(['KB', '饰品']);

expect(paperStretcher.variants[0].options.map(byName).sort()).toEqual(['半英制']);
expect(paperStretcher.variants[1].options.map(byName).sort()).toEqual(['四分之一英制']);
expect(paperStretcher.variants[2].options.map(byName).sort()).toEqual(['全英制']);

// Import list custom fields
expect(paperStretcher.customFields.keywords).toEqual(['paper, stretch']);

// Import localeString custom fields
expect(paperStretcher.customFields.localName).toEqual('纸张拉伸器');
}, 20000);
});
Loading