@shopify/cli-hydrogen 5.0.2 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/hydrogen/build.js +16 -2
- package/dist/commands/hydrogen/codegen-unstable.js +13 -24
- package/dist/commands/hydrogen/dev.js +45 -39
- package/dist/commands/hydrogen/env/list.js +25 -24
- package/dist/commands/hydrogen/env/list.test.js +46 -43
- package/dist/commands/hydrogen/env/pull.js +53 -25
- package/dist/commands/hydrogen/env/pull.test.js +123 -42
- package/dist/commands/hydrogen/generate/route.js +31 -132
- package/dist/commands/hydrogen/generate/route.test.js +34 -126
- package/dist/commands/hydrogen/init.js +46 -127
- package/dist/commands/hydrogen/init.test.js +352 -100
- package/dist/commands/hydrogen/link.js +70 -69
- package/dist/commands/hydrogen/link.test.js +72 -107
- package/dist/commands/hydrogen/list.js +22 -12
- package/dist/commands/hydrogen/list.test.js +51 -48
- package/dist/commands/hydrogen/login.js +31 -0
- package/dist/commands/hydrogen/logout.js +21 -0
- package/dist/commands/hydrogen/setup/css.js +79 -0
- package/dist/commands/hydrogen/setup/markets.js +53 -0
- package/dist/commands/hydrogen/setup.js +133 -0
- package/dist/commands/hydrogen/shortcut.js +2 -45
- package/dist/commands/hydrogen/shortcut.test.js +10 -37
- package/dist/generator-templates/assets/css-modules/package.json +6 -0
- package/dist/generator-templates/assets/postcss/package.json +10 -0
- package/dist/generator-templates/assets/postcss/postcss.config.js +8 -0
- package/dist/generator-templates/assets/tailwind/package.json +13 -0
- package/dist/generator-templates/assets/tailwind/postcss.config.js +10 -0
- package/dist/generator-templates/assets/tailwind/tailwind.config.js +8 -0
- package/dist/generator-templates/assets/tailwind/tailwind.css +3 -0
- package/dist/generator-templates/assets/vanilla-extract/package.json +9 -0
- package/dist/generator-templates/starter/.eslintignore +5 -0
- package/dist/generator-templates/starter/.eslintrc.js +18 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +1 -0
- package/dist/generator-templates/starter/README.md +40 -0
- package/dist/generator-templates/starter/app/components/Aside.tsx +47 -0
- package/dist/generator-templates/starter/app/components/Cart.tsx +340 -0
- package/dist/generator-templates/starter/app/components/Footer.tsx +99 -0
- package/dist/generator-templates/starter/app/components/Header.tsx +178 -0
- package/dist/generator-templates/starter/app/components/Layout.tsx +95 -0
- package/dist/generator-templates/starter/app/components/Search.tsx +480 -0
- package/dist/generator-templates/starter/app/entry.client.tsx +12 -0
- package/dist/generator-templates/starter/app/entry.server.tsx +33 -0
- package/dist/generator-templates/starter/app/root.tsx +270 -0
- package/dist/generator-templates/starter/app/routes/$.tsx +7 -0
- package/dist/generator-templates/{routes → starter/app/routes}/[robots.txt].tsx +47 -69
- package/dist/generator-templates/starter/app/routes/[sitemap.xml].tsx +174 -0
- package/dist/generator-templates/starter/app/routes/_index.tsx +145 -0
- package/dist/generator-templates/starter/app/routes/account.$.tsx +9 -0
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +563 -0
- package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +309 -0
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +196 -0
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +289 -0
- package/dist/generator-templates/starter/app/routes/account.tsx +203 -0
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +157 -0
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +143 -0
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +33 -0
- package/dist/generator-templates/starter/app/routes/account_.recover.tsx +124 -0
- package/dist/generator-templates/starter/app/routes/account_.register.tsx +207 -0
- package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +136 -0
- package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +342 -0
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +88 -0
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +162 -0
- package/dist/generator-templates/starter/app/routes/blogs._index.tsx +94 -0
- package/dist/generator-templates/starter/app/routes/cart.tsx +104 -0
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +184 -0
- package/dist/generator-templates/starter/app/routes/collections._index.tsx +120 -0
- package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +57 -0
- package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +94 -0
- package/dist/generator-templates/starter/app/routes/policies._index.tsx +63 -0
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +418 -0
- package/dist/generator-templates/starter/app/routes/search.tsx +168 -0
- package/dist/generator-templates/starter/app/styles/app.css +473 -0
- package/dist/generator-templates/starter/app/styles/reset.css +129 -0
- package/dist/generator-templates/starter/app/utils.ts +46 -0
- package/dist/generator-templates/starter/package.json +43 -0
- package/dist/generator-templates/starter/public/favicon.svg +28 -0
- package/dist/generator-templates/starter/remix.config.js +26 -0
- package/dist/generator-templates/starter/remix.env.d.ts +39 -0
- package/dist/generator-templates/starter/server.ts +253 -0
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +1906 -0
- package/dist/generator-templates/starter/tsconfig.json +22 -0
- package/dist/lib/auth.js +123 -0
- package/dist/lib/auth.test.js +157 -0
- package/dist/lib/build.js +51 -0
- package/dist/lib/check-version.js +3 -3
- package/dist/lib/check-version.test.js +24 -0
- package/dist/lib/codegen.js +26 -17
- package/dist/lib/environment-variables.js +68 -0
- package/dist/lib/environment-variables.test.js +147 -0
- package/dist/lib/file.js +41 -0
- package/dist/lib/file.test.js +69 -0
- package/dist/lib/flags.js +39 -2
- package/dist/lib/format-code.js +26 -0
- package/dist/lib/gid.js +12 -0
- package/dist/lib/{graphql.test.js → gid.test.js} +1 -1
- package/dist/lib/graphql/admin/client.js +27 -0
- package/dist/lib/graphql/admin/client.test.js +51 -0
- package/dist/lib/graphql/admin/create-storefront.js +13 -15
- package/dist/lib/graphql/admin/create-storefront.test.js +64 -0
- package/dist/lib/graphql/admin/fetch-job.js +6 -15
- package/dist/lib/graphql/admin/link-storefront.js +7 -11
- package/dist/lib/graphql/admin/link-storefront.test.js +38 -0
- package/dist/lib/graphql/admin/list-environments.js +2 -2
- package/dist/lib/graphql/admin/list-environments.test.js +44 -0
- package/dist/lib/graphql/admin/list-storefronts.js +7 -11
- package/dist/lib/graphql/admin/list-storefronts.test.js +44 -0
- package/dist/lib/graphql/admin/pull-variables.js +3 -3
- package/dist/lib/graphql/admin/pull-variables.test.js +37 -0
- package/dist/lib/graphql/business-platform/user-account.js +83 -0
- package/dist/lib/graphql/business-platform/user-account.test.js +80 -0
- package/dist/lib/log.js +185 -9
- package/dist/lib/log.test.js +92 -0
- package/dist/lib/mini-oxygen.js +19 -9
- package/dist/lib/missing-routes.js +0 -2
- package/dist/lib/onboarding/common.js +456 -0
- package/dist/lib/onboarding/index.js +2 -0
- package/dist/lib/onboarding/local.js +229 -0
- package/dist/lib/onboarding/remote.js +89 -0
- package/dist/lib/remix-version-interop.js +5 -5
- package/dist/lib/remix-version-interop.test.js +11 -1
- package/dist/lib/render-errors.js +13 -11
- package/dist/lib/setups/css/assets.js +89 -0
- package/dist/lib/setups/css/css-modules.js +22 -0
- package/dist/lib/setups/css/index.js +44 -0
- package/dist/lib/setups/css/postcss.js +34 -0
- package/dist/lib/setups/css/replacers.js +137 -0
- package/dist/lib/setups/css/tailwind.js +54 -0
- package/dist/lib/setups/css/vanilla-extract.js +22 -0
- package/dist/lib/setups/i18n/domains.test.js +25 -0
- package/dist/lib/setups/i18n/index.js +46 -0
- package/dist/lib/setups/i18n/replacers.js +227 -0
- package/dist/lib/setups/i18n/subdomains.test.js +25 -0
- package/dist/lib/setups/i18n/subfolders.test.js +25 -0
- package/dist/lib/setups/i18n/templates/domains.js +14 -0
- package/dist/lib/setups/i18n/templates/domains.ts +25 -0
- package/dist/lib/setups/i18n/templates/subdomains.js +14 -0
- package/dist/lib/setups/i18n/templates/subdomains.ts +24 -0
- package/dist/lib/setups/i18n/templates/subfolders.js +14 -0
- package/dist/lib/setups/i18n/templates/subfolders.ts +28 -0
- package/dist/lib/setups/routes/generate.js +244 -0
- package/dist/lib/setups/routes/generate.test.js +313 -0
- package/dist/lib/shell.js +52 -5
- package/dist/lib/shell.test.js +42 -16
- package/dist/lib/shopify-config.js +23 -18
- package/dist/lib/shopify-config.test.js +63 -73
- package/dist/lib/template-downloader.js +9 -7
- package/dist/lib/transpile-ts.js +9 -29
- package/dist/virtual-routes/routes/index.jsx +40 -19
- package/oclif.manifest.json +710 -1
- package/package.json +16 -16
- package/dist/commands/hydrogen/build.d.ts +0 -23
- package/dist/commands/hydrogen/check.d.ts +0 -15
- package/dist/commands/hydrogen/codegen-unstable.d.ts +0 -15
- package/dist/commands/hydrogen/dev.d.ts +0 -21
- package/dist/commands/hydrogen/env/list.d.ts +0 -18
- package/dist/commands/hydrogen/env/pull.d.ts +0 -22
- package/dist/commands/hydrogen/g.d.ts +0 -10
- package/dist/commands/hydrogen/generate/route.d.ts +0 -32
- package/dist/commands/hydrogen/generate/route.test.d.ts +0 -1
- package/dist/commands/hydrogen/generate/routes.d.ts +0 -16
- package/dist/commands/hydrogen/init.d.ts +0 -24
- package/dist/commands/hydrogen/init.test.d.ts +0 -1
- package/dist/commands/hydrogen/link.d.ts +0 -23
- package/dist/commands/hydrogen/link.test.d.ts +0 -1
- package/dist/commands/hydrogen/list.d.ts +0 -21
- package/dist/commands/hydrogen/list.test.d.ts +0 -1
- package/dist/commands/hydrogen/preview.d.ts +0 -17
- package/dist/commands/hydrogen/shortcut.d.ts +0 -9
- package/dist/commands/hydrogen/shortcut.test.d.ts +0 -1
- package/dist/commands/hydrogen/unlink.d.ts +0 -16
- package/dist/commands/hydrogen/unlink.test.d.ts +0 -1
- package/dist/create-app.d.ts +0 -1
- package/dist/generator-templates/routes/[sitemap.xml].tsx +0 -235
- package/dist/generator-templates/routes/account/login.tsx +0 -103
- package/dist/generator-templates/routes/account/register.tsx +0 -103
- package/dist/generator-templates/routes/cart.tsx +0 -81
- package/dist/generator-templates/routes/collections/$collectionHandle.tsx +0 -104
- package/dist/generator-templates/routes/collections/index.tsx +0 -102
- package/dist/generator-templates/routes/graphiql.tsx +0 -10
- package/dist/generator-templates/routes/index.tsx +0 -40
- package/dist/generator-templates/routes/pages/$pageHandle.tsx +0 -112
- package/dist/generator-templates/routes/policies/$policyHandle.tsx +0 -140
- package/dist/generator-templates/routes/policies/index.tsx +0 -117
- package/dist/generator-templates/routes/products/$productHandle.tsx +0 -92
- package/dist/hooks/init.d.ts +0 -5
- package/dist/lib/admin-session.d.ts +0 -6
- package/dist/lib/admin-session.js +0 -16
- package/dist/lib/admin-session.test.d.ts +0 -1
- package/dist/lib/admin-session.test.js +0 -27
- package/dist/lib/admin-urls.d.ts +0 -8
- package/dist/lib/check-lockfile.d.ts +0 -3
- package/dist/lib/check-lockfile.test.d.ts +0 -1
- package/dist/lib/check-version.d.ts +0 -16
- package/dist/lib/check-version.test.d.ts +0 -1
- package/dist/lib/codegen.d.ts +0 -26
- package/dist/lib/combined-environment-variables.d.ts +0 -8
- package/dist/lib/combined-environment-variables.js +0 -57
- package/dist/lib/combined-environment-variables.test.d.ts +0 -1
- package/dist/lib/combined-environment-variables.test.js +0 -111
- package/dist/lib/config.d.ts +0 -20
- package/dist/lib/flags.d.ts +0 -27
- package/dist/lib/flags.test.d.ts +0 -1
- package/dist/lib/graphql/admin/create-storefront.d.ts +0 -17
- package/dist/lib/graphql/admin/fetch-job.d.ts +0 -23
- package/dist/lib/graphql/admin/link-storefront.d.ts +0 -14
- package/dist/lib/graphql/admin/list-environments.d.ts +0 -21
- package/dist/lib/graphql/admin/list-storefronts.d.ts +0 -25
- package/dist/lib/graphql/admin/pull-variables.d.ts +0 -21
- package/dist/lib/graphql.d.ts +0 -21
- package/dist/lib/graphql.js +0 -18
- package/dist/lib/graphql.test.d.ts +0 -1
- package/dist/lib/log.d.ts +0 -6
- package/dist/lib/mini-oxygen.d.ts +0 -22
- package/dist/lib/missing-routes.d.ts +0 -8
- package/dist/lib/missing-routes.test.d.ts +0 -1
- package/dist/lib/missing-storefronts.d.ts +0 -5
- package/dist/lib/missing-storefronts.js +0 -18
- package/dist/lib/process.d.ts +0 -6
- package/dist/lib/pull-environment-variables.d.ts +0 -20
- package/dist/lib/pull-environment-variables.js +0 -57
- package/dist/lib/pull-environment-variables.test.d.ts +0 -1
- package/dist/lib/pull-environment-variables.test.js +0 -174
- package/dist/lib/remix-version-interop.d.ts +0 -11
- package/dist/lib/remix-version-interop.test.d.ts +0 -1
- package/dist/lib/render-errors.d.ts +0 -16
- package/dist/lib/shell.d.ts +0 -11
- package/dist/lib/shell.test.d.ts +0 -1
- package/dist/lib/shop.d.ts +0 -7
- package/dist/lib/shop.js +0 -32
- package/dist/lib/shop.test.d.ts +0 -1
- package/dist/lib/shop.test.js +0 -78
- package/dist/lib/shopify-config.d.ts +0 -35
- package/dist/lib/shopify-config.test.d.ts +0 -1
- package/dist/lib/string.d.ts +0 -3
- package/dist/lib/string.test.d.ts +0 -1
- package/dist/lib/template-downloader.d.ts +0 -6
- package/dist/lib/transpile-ts.d.ts +0 -16
- package/dist/lib/user-errors.d.ts +0 -9
- package/dist/lib/user-errors.js +0 -11
- package/dist/lib/virtual-routes.d.ts +0 -7
- package/dist/lib/virtual-routes.test.d.ts +0 -1
- /package/dist/{commands/hydrogen/env/list.test.d.ts → lib/setups/css/common.js} +0 -0
- /package/dist/{commands/hydrogen/env/pull.test.d.ts → lib/setups/i18n/mock-i18n-types.js} +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
2
|
+
import { ts, tsx, js, jsx } from '@ast-grep/napi';
|
|
3
|
+
import { findFileWithExtension, replaceFileContent } from '../../file.js';
|
|
4
|
+
|
|
5
|
+
const astGrep = { ts, tsx, js, jsx };
|
|
6
|
+
async function replaceRemixConfig(rootDirectory, formatConfig, newProperties) {
|
|
7
|
+
const { filepath, astType } = await findFileWithExtension(
|
|
8
|
+
rootDirectory,
|
|
9
|
+
"remix.config"
|
|
10
|
+
);
|
|
11
|
+
if (!filepath || !astType) {
|
|
12
|
+
throw new AbortError(
|
|
13
|
+
`Could not find remix.config.js file in ${rootDirectory}`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
await replaceFileContent(filepath, formatConfig, async (content) => {
|
|
17
|
+
const root = astGrep[astType].parse(content).root();
|
|
18
|
+
const remixConfigNode = root.find({
|
|
19
|
+
rule: {
|
|
20
|
+
kind: "object",
|
|
21
|
+
inside: {
|
|
22
|
+
any: [
|
|
23
|
+
{
|
|
24
|
+
kind: "export_statement"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
kind: "assignment_expression",
|
|
28
|
+
has: {
|
|
29
|
+
kind: "member_expression",
|
|
30
|
+
field: "left",
|
|
31
|
+
pattern: "module.exports"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if (!remixConfigNode) {
|
|
39
|
+
throw new AbortError(
|
|
40
|
+
"Could not find a default export in remix.config.js"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
newProperties = { ...newProperties };
|
|
44
|
+
for (const key of Object.keys(newProperties)) {
|
|
45
|
+
const propertyNode = remixConfigNode.find({
|
|
46
|
+
rule: {
|
|
47
|
+
kind: "pair",
|
|
48
|
+
has: {
|
|
49
|
+
field: "key",
|
|
50
|
+
regex: `^${key}$`
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (propertyNode?.text().endsWith(" " + JSON.stringify(newProperties[key]))) {
|
|
55
|
+
delete newProperties[key];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (Object.keys(newProperties).length === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const childrenNodes = remixConfigNode.children();
|
|
62
|
+
const lastNode = childrenNodes.find((node) => node.text().startsWith("future:")) ?? childrenNodes.pop();
|
|
63
|
+
if (!lastNode) {
|
|
64
|
+
throw new AbortError("Could not add properties to Remix config");
|
|
65
|
+
}
|
|
66
|
+
const { start } = lastNode.range();
|
|
67
|
+
return content.slice(0, start.index) + JSON.stringify(newProperties).slice(1, -1) + "," + content.slice(start.index);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function replaceRootLinks(appDirectory, formatConfig, importer) {
|
|
71
|
+
const { filepath, astType } = await findFileWithExtension(appDirectory, "root");
|
|
72
|
+
if (!filepath || !astType) {
|
|
73
|
+
throw new AbortError(`Could not find root file in ${appDirectory}`);
|
|
74
|
+
}
|
|
75
|
+
await replaceFileContent(filepath, formatConfig, async (content) => {
|
|
76
|
+
const importStatement = `import ${importer.isDefault ? importer.name : `{${importer.name}}`} from '${(importer.isAbsolute ? "" : "./") + importer.path}';`;
|
|
77
|
+
if (content.includes(importStatement.split("from")[0])) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const root = astGrep[astType].parse(content).root();
|
|
81
|
+
const lastImportNode = root.findAll({ rule: { kind: "import_statement" } }).pop();
|
|
82
|
+
const linksReturnNode = root.find({
|
|
83
|
+
utils: {
|
|
84
|
+
"has-links-id": {
|
|
85
|
+
has: {
|
|
86
|
+
kind: "identifier",
|
|
87
|
+
pattern: "links"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
rule: {
|
|
92
|
+
kind: "return_statement",
|
|
93
|
+
pattern: "return [$$$]",
|
|
94
|
+
inside: {
|
|
95
|
+
any: [
|
|
96
|
+
{
|
|
97
|
+
kind: "function_declaration",
|
|
98
|
+
matches: "has-links-id"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
kind: "variable_declarator",
|
|
102
|
+
matches: "has-links-id"
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
stopBy: "end",
|
|
106
|
+
inside: {
|
|
107
|
+
stopBy: "end",
|
|
108
|
+
kind: "export_statement"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
if (!lastImportNode || !linksReturnNode) {
|
|
114
|
+
throw new AbortError(
|
|
115
|
+
'Could not find a "links" export in root file. Please add one and try again.'
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const lastImportContent = lastImportNode.text();
|
|
119
|
+
const linksExportReturnContent = linksReturnNode.text();
|
|
120
|
+
const newLinkReturnItem = importer.isConditional ? `...(${importer.name} ? [{ rel: 'stylesheet', href: ${importer.name} }] : [])` : `{rel: 'stylesheet', href: ${importer.name}}`;
|
|
121
|
+
return content.replace(lastImportContent, lastImportContent + "\n" + importStatement).replace(
|
|
122
|
+
linksExportReturnContent,
|
|
123
|
+
linksExportReturnContent.replace("[", `[${newLinkReturnItem},`)
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function injectCssBundlingLink(appDirectory, formatConfig) {
|
|
128
|
+
return replaceRootLinks(appDirectory, formatConfig, {
|
|
129
|
+
name: "cssBundleHref",
|
|
130
|
+
path: "@remix-run/css-bundle",
|
|
131
|
+
isDefault: false,
|
|
132
|
+
isConditional: true,
|
|
133
|
+
isAbsolute: true
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { injectCssBundlingLink, replaceRemixConfig, replaceRootLinks };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { outputInfo } from '@shopify/cli-kit/node/output';
|
|
2
|
+
import { relativePath, joinPath } from '@shopify/cli-kit/node/path';
|
|
3
|
+
import { canWriteFiles, mergePackageJson, copyAssets } from './assets.js';
|
|
4
|
+
import { getCodeFormatOptions } from '../../format-code.js';
|
|
5
|
+
import { replaceRemixConfig, replaceRootLinks } from './replacers.js';
|
|
6
|
+
|
|
7
|
+
const tailwindCssPath = "styles/tailwind.css";
|
|
8
|
+
async function setupTailwind({ rootDirectory, appDirectory, ...futureOptions }, force = false) {
|
|
9
|
+
const relativeAppDirectory = relativePath(rootDirectory, appDirectory);
|
|
10
|
+
const assetMap = {
|
|
11
|
+
"tailwind.config.js": "tailwind.config.js",
|
|
12
|
+
"postcss.config.js": "postcss.config.js",
|
|
13
|
+
"tailwind.css": joinPath(relativeAppDirectory, tailwindCssPath)
|
|
14
|
+
};
|
|
15
|
+
if (futureOptions.tailwind && futureOptions.postcss) {
|
|
16
|
+
outputInfo(`Tailwind and PostCSS are already setup in ${rootDirectory}.`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!await canWriteFiles(assetMap, appDirectory, force)) {
|
|
20
|
+
outputInfo(
|
|
21
|
+
`Skipping CSS setup as some files already exist. You may use \`--force\` or \`-f\` to override it.`
|
|
22
|
+
);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const workPromise = Promise.all([
|
|
26
|
+
mergePackageJson("tailwind", rootDirectory),
|
|
27
|
+
copyAssets(
|
|
28
|
+
"tailwind",
|
|
29
|
+
assetMap,
|
|
30
|
+
rootDirectory,
|
|
31
|
+
(content, filepath) => filepath === "tailwind.config.js" ? content.replace("{src-dir}", relativeAppDirectory) : content
|
|
32
|
+
),
|
|
33
|
+
getCodeFormatOptions(rootDirectory).then(
|
|
34
|
+
(formatConfig) => Promise.all([
|
|
35
|
+
replaceRemixConfig(rootDirectory, formatConfig, {
|
|
36
|
+
tailwind: true,
|
|
37
|
+
postcss: true
|
|
38
|
+
}),
|
|
39
|
+
replaceRootLinks(appDirectory, formatConfig, {
|
|
40
|
+
name: "tailwindCss",
|
|
41
|
+
path: tailwindCssPath,
|
|
42
|
+
isDefault: true
|
|
43
|
+
})
|
|
44
|
+
])
|
|
45
|
+
)
|
|
46
|
+
]);
|
|
47
|
+
return {
|
|
48
|
+
workPromise,
|
|
49
|
+
generatedAssets: Object.values(assetMap),
|
|
50
|
+
helpUrl: "https://tailwindcss.com/docs/configuration"
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { setupTailwind };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mergePackageJson } from './assets.js';
|
|
2
|
+
import { getCodeFormatOptions } from '../../format-code.js';
|
|
3
|
+
import { injectCssBundlingLink } from './replacers.js';
|
|
4
|
+
|
|
5
|
+
async function setupVanillaExtract({
|
|
6
|
+
rootDirectory,
|
|
7
|
+
appDirectory
|
|
8
|
+
}) {
|
|
9
|
+
const workPromise = Promise.all([
|
|
10
|
+
mergePackageJson("vanilla-extract", rootDirectory),
|
|
11
|
+
getCodeFormatOptions(rootDirectory).then(
|
|
12
|
+
(formatConfig) => injectCssBundlingLink(appDirectory, formatConfig)
|
|
13
|
+
)
|
|
14
|
+
]);
|
|
15
|
+
return {
|
|
16
|
+
workPromise,
|
|
17
|
+
generatedAssets: [],
|
|
18
|
+
helpUrl: "https://vanilla-extract.style/documentation/styling/"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { setupVanillaExtract };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getLocaleFromRequest } from './templates/domains.js';
|
|
3
|
+
|
|
4
|
+
describe("Setup i18n with domains", () => {
|
|
5
|
+
it("extracts the locale from the domain", () => {
|
|
6
|
+
expect(
|
|
7
|
+
getLocaleFromRequest(new Request("https://example.com"))
|
|
8
|
+
).toMatchObject({
|
|
9
|
+
language: "EN",
|
|
10
|
+
country: "US"
|
|
11
|
+
});
|
|
12
|
+
expect(
|
|
13
|
+
getLocaleFromRequest(new Request("https://example.jp"))
|
|
14
|
+
).toMatchObject({
|
|
15
|
+
language: "JA",
|
|
16
|
+
country: "JP"
|
|
17
|
+
});
|
|
18
|
+
expect(
|
|
19
|
+
getLocaleFromRequest(new Request("https://www.example.es"))
|
|
20
|
+
).toMatchObject({
|
|
21
|
+
language: "ES",
|
|
22
|
+
country: "ES"
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import { renderSelectPrompt } from '@shopify/cli-kit/node/ui';
|
|
3
|
+
import { fileExists, readFile } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { getCodeFormatOptions } from '../../format-code.js';
|
|
5
|
+
import { replaceServerI18n, replaceRemixEnv } from './replacers.js';
|
|
6
|
+
|
|
7
|
+
const SETUP_I18N_STRATEGIES = [
|
|
8
|
+
"subfolders",
|
|
9
|
+
"domains",
|
|
10
|
+
"subdomains"
|
|
11
|
+
];
|
|
12
|
+
const I18N_STRATEGY_NAME_MAP = {
|
|
13
|
+
subfolders: "Subfolders (example.com/fr-ca/...)",
|
|
14
|
+
subdomains: "Subdomains (de.example.com/...)",
|
|
15
|
+
domains: "Top-level domains (example.jp/...)"
|
|
16
|
+
};
|
|
17
|
+
const I18N_CHOICES = [...SETUP_I18N_STRATEGIES, "none"];
|
|
18
|
+
async function setupI18nStrategy(strategy, options) {
|
|
19
|
+
const isTs = options.serverEntryPoint?.endsWith(".ts") ?? false;
|
|
20
|
+
const templatePath = fileURLToPath(
|
|
21
|
+
new URL(`./templates/${strategy}${isTs ? ".ts" : ".js"}`, import.meta.url)
|
|
22
|
+
);
|
|
23
|
+
if (!await fileExists(templatePath)) {
|
|
24
|
+
throw new Error("Unknown strategy");
|
|
25
|
+
}
|
|
26
|
+
const template = await readFile(templatePath);
|
|
27
|
+
const formatConfig = await getCodeFormatOptions(options.rootDirectory);
|
|
28
|
+
await replaceServerI18n(options, formatConfig, template);
|
|
29
|
+
await replaceRemixEnv(options, formatConfig, template);
|
|
30
|
+
}
|
|
31
|
+
async function renderI18nPrompt(options) {
|
|
32
|
+
const i18nStrategies = Object.entries({
|
|
33
|
+
...I18N_STRATEGY_NAME_MAP,
|
|
34
|
+
...options?.extraChoices
|
|
35
|
+
});
|
|
36
|
+
return renderSelectPrompt({
|
|
37
|
+
message: "Select a URL structure to support multiple markets",
|
|
38
|
+
...options,
|
|
39
|
+
choices: i18nStrategies.map(([value, label]) => ({
|
|
40
|
+
value,
|
|
41
|
+
label
|
|
42
|
+
}))
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { I18N_CHOICES, I18N_STRATEGY_NAME_MAP, SETUP_I18N_STRATEGIES, renderI18nPrompt, setupI18nStrategy };
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { AbortError } from '@shopify/cli-kit/node/error';
|
|
2
|
+
import { joinPath, relativePath } from '@shopify/cli-kit/node/path';
|
|
3
|
+
import { fileExists } from '@shopify/cli-kit/node/fs';
|
|
4
|
+
import { ts, tsx, js, jsx } from '@ast-grep/napi';
|
|
5
|
+
import { replaceFileContent, findFileWithExtension } from '../../file.js';
|
|
6
|
+
|
|
7
|
+
const astGrep = { ts, tsx, js, jsx };
|
|
8
|
+
async function replaceServerI18n({ rootDirectory, serverEntryPoint = "server" }, formatConfig, localeExtractImplementation) {
|
|
9
|
+
const { filepath, astType } = await findEntryFile({
|
|
10
|
+
rootDirectory,
|
|
11
|
+
serverEntryPoint
|
|
12
|
+
});
|
|
13
|
+
await replaceFileContent(filepath, formatConfig, async (content) => {
|
|
14
|
+
const root = astGrep[astType].parse(content).root();
|
|
15
|
+
const requestIdentifier = root.find({
|
|
16
|
+
rule: {
|
|
17
|
+
kind: "identifier",
|
|
18
|
+
inside: {
|
|
19
|
+
kind: "formal_parameters",
|
|
20
|
+
stopBy: "end",
|
|
21
|
+
inside: {
|
|
22
|
+
kind: "method_definition",
|
|
23
|
+
stopBy: "end",
|
|
24
|
+
has: {
|
|
25
|
+
kind: "property_identifier",
|
|
26
|
+
regex: "^fetch$"
|
|
27
|
+
},
|
|
28
|
+
inside: {
|
|
29
|
+
kind: "export_statement",
|
|
30
|
+
stopBy: "end"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
const requestIdentifierName = requestIdentifier?.text() ?? "request";
|
|
37
|
+
const i18nFunctionName = localeExtractImplementation.match(
|
|
38
|
+
/^(export )?function (\w+)/m
|
|
39
|
+
)?.[2];
|
|
40
|
+
if (!i18nFunctionName) {
|
|
41
|
+
throw new Error("Could not find the i18n function name");
|
|
42
|
+
}
|
|
43
|
+
const i18nFunctionCall = `${i18nFunctionName}(${requestIdentifierName})`;
|
|
44
|
+
const hydrogenImportPath = "@shopify/hydrogen";
|
|
45
|
+
const hydrogenImportName = "createStorefrontClient";
|
|
46
|
+
const importSpecifier = root.find({
|
|
47
|
+
rule: {
|
|
48
|
+
kind: "import_specifier",
|
|
49
|
+
inside: {
|
|
50
|
+
kind: "import_statement",
|
|
51
|
+
stopBy: "end",
|
|
52
|
+
has: {
|
|
53
|
+
kind: "string_fragment",
|
|
54
|
+
stopBy: "end",
|
|
55
|
+
regex: `^${hydrogenImportPath}$`
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
has: {
|
|
59
|
+
kind: "identifier",
|
|
60
|
+
regex: `^${hydrogenImportName}`
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
let [importName, importAlias] = importSpecifier?.text().split(/\s+as\s+/) || [];
|
|
65
|
+
importName = importAlias ?? importName;
|
|
66
|
+
if (!importName) {
|
|
67
|
+
throw new AbortError(
|
|
68
|
+
`Could not find a Hydrogen import in ${serverEntryPoint}`,
|
|
69
|
+
`Please import "${hydrogenImportName}" from "${hydrogenImportPath}"`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const argumentObject = root.find({
|
|
73
|
+
rule: {
|
|
74
|
+
kind: "object",
|
|
75
|
+
inside: {
|
|
76
|
+
kind: "arguments",
|
|
77
|
+
inside: {
|
|
78
|
+
kind: "call_expression",
|
|
79
|
+
has: {
|
|
80
|
+
kind: "identifier",
|
|
81
|
+
regex: importName
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
if (!argumentObject) {
|
|
88
|
+
throw new AbortError(
|
|
89
|
+
`Could not find a Hydrogen client instantiation with an inline object as argument in ${serverEntryPoint}`,
|
|
90
|
+
`Please add a call to ${importName}({...})`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const i18nProperty = argumentObject.find({
|
|
94
|
+
rule: {
|
|
95
|
+
kind: "property_identifier",
|
|
96
|
+
regex: "^i18n$"
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
const i18nValue = i18nProperty?.next()?.next();
|
|
100
|
+
if (i18nValue) {
|
|
101
|
+
if (i18nValue.text().includes(i18nFunctionName)) {
|
|
102
|
+
throw new AbortError(
|
|
103
|
+
"An i18n strategy is already set up.",
|
|
104
|
+
`Remove the existing i18n property in the ${importName}({...}) call to set up a new one.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const { start, end } = i18nValue.range();
|
|
108
|
+
content = content.slice(0, start.index) + i18nFunctionCall + content.slice(end.index);
|
|
109
|
+
} else {
|
|
110
|
+
const { end } = argumentObject.range();
|
|
111
|
+
const firstPart = content.slice(0, end.index - 1);
|
|
112
|
+
content = firstPart + ((/,\s*$/.test(firstPart) ? "" : ",") + `i18n: ${i18nFunctionCall}`) + content.slice(end.index - 1);
|
|
113
|
+
}
|
|
114
|
+
const importTypes = localeExtractImplementation.match(
|
|
115
|
+
/import\s+type\s+[^;]+?;/
|
|
116
|
+
)?.[0];
|
|
117
|
+
if (importTypes) {
|
|
118
|
+
localeExtractImplementation = localeExtractImplementation.replace(
|
|
119
|
+
importTypes,
|
|
120
|
+
""
|
|
121
|
+
);
|
|
122
|
+
const lastImportNode = root.findAll({ rule: { kind: "import_statement" } }).pop();
|
|
123
|
+
if (lastImportNode) {
|
|
124
|
+
const lastImportContent = lastImportNode.text();
|
|
125
|
+
content = content.replace(
|
|
126
|
+
lastImportContent,
|
|
127
|
+
lastImportContent + "\n" + importTypes.replace(
|
|
128
|
+
/'[^']+'/,
|
|
129
|
+
`'@shopify/hydrogen/storefront-api-types'`
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return content + `
|
|
135
|
+
|
|
136
|
+
${localeExtractImplementation.replace(/^export function/m, "function").replace(/^export {.*?;/m, "")}
|
|
137
|
+
`;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function replaceRemixEnv({ rootDirectory, serverEntryPoint }, formatConfig, localeExtractImplementation) {
|
|
141
|
+
const remixEnvPath = joinPath(rootDirectory, "remix.env.d.ts");
|
|
142
|
+
if (!await fileExists(remixEnvPath)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const i18nTypeName = localeExtractImplementation.match(/export type (\w+)/)?.[1];
|
|
146
|
+
if (!i18nTypeName) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const { filepath: entryFilepath } = await findEntryFile({
|
|
150
|
+
rootDirectory,
|
|
151
|
+
serverEntryPoint
|
|
152
|
+
});
|
|
153
|
+
const relativePathToEntry = relativePath(
|
|
154
|
+
rootDirectory,
|
|
155
|
+
entryFilepath
|
|
156
|
+
).replace(/.[tj]sx?$/, "");
|
|
157
|
+
await replaceFileContent(remixEnvPath, formatConfig, (content) => {
|
|
158
|
+
if (content.includes(`Storefront<`))
|
|
159
|
+
return;
|
|
160
|
+
const root = astGrep.ts.parse(content).root();
|
|
161
|
+
const storefrontTypeNode = root.find({
|
|
162
|
+
rule: {
|
|
163
|
+
kind: "property_signature",
|
|
164
|
+
has: {
|
|
165
|
+
kind: "type_annotation",
|
|
166
|
+
has: {
|
|
167
|
+
regex: "^Storefront$"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
inside: {
|
|
171
|
+
kind: "interface_declaration",
|
|
172
|
+
stopBy: "end",
|
|
173
|
+
regex: "AppLoadContext"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (!storefrontTypeNode) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const { end } = storefrontTypeNode.range();
|
|
181
|
+
content = content.slice(0, end.index) + `<${i18nTypeName}>` + content.slice(end.index);
|
|
182
|
+
const serverImportNode = root.findAll({
|
|
183
|
+
rule: {
|
|
184
|
+
kind: "import_statement",
|
|
185
|
+
has: {
|
|
186
|
+
kind: "string_fragment",
|
|
187
|
+
stopBy: "end",
|
|
188
|
+
regex: `^(\\./)?${relativePathToEntry.replaceAll(
|
|
189
|
+
".",
|
|
190
|
+
"\\."
|
|
191
|
+
)}(\\.[jt]sx?)?$`
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}).pop();
|
|
195
|
+
if (serverImportNode) {
|
|
196
|
+
content = content.replace(
|
|
197
|
+
serverImportNode.text(),
|
|
198
|
+
serverImportNode.text().replace("{", `{${i18nTypeName},`)
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
const lastImportNode = root.findAll({ rule: { kind: "import_statement" } }).pop() ?? root.findAll({ rule: { kind: "comment", regex: "^/// <reference" } }).pop();
|
|
202
|
+
const { end: end2 } = lastImportNode?.range() ?? { end: { index: 0 } };
|
|
203
|
+
const typeImport = `
|
|
204
|
+
import type {${i18nTypeName}} from './${serverEntryPoint.replace(
|
|
205
|
+
/\.[jt]s$/,
|
|
206
|
+
""
|
|
207
|
+
)}';`;
|
|
208
|
+
content = content.slice(0, end2.index) + typeImport + content.slice(end2.index);
|
|
209
|
+
}
|
|
210
|
+
return content;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function findEntryFile({
|
|
214
|
+
rootDirectory,
|
|
215
|
+
serverEntryPoint = "server"
|
|
216
|
+
}) {
|
|
217
|
+
const match = serverEntryPoint.match(/\.([jt]sx?)$/)?.[1];
|
|
218
|
+
const { filepath, astType } = match ? { filepath: joinPath(rootDirectory, serverEntryPoint), astType: match } : await findFileWithExtension(rootDirectory, serverEntryPoint);
|
|
219
|
+
if (!filepath || !astType) {
|
|
220
|
+
throw new AbortError(
|
|
221
|
+
`Could not find a server entry point at ${serverEntryPoint}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
return { filepath, astType };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { replaceRemixEnv, replaceServerI18n };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getLocaleFromRequest } from './templates/subdomains.js';
|
|
3
|
+
|
|
4
|
+
describe("Setup i18n with subdomains", () => {
|
|
5
|
+
it("extracts the locale from the subdomain", () => {
|
|
6
|
+
expect(
|
|
7
|
+
getLocaleFromRequest(new Request("https://example.com"))
|
|
8
|
+
).toMatchObject({
|
|
9
|
+
language: "EN",
|
|
10
|
+
country: "US"
|
|
11
|
+
});
|
|
12
|
+
expect(
|
|
13
|
+
getLocaleFromRequest(new Request("https://jp.example.com"))
|
|
14
|
+
).toMatchObject({
|
|
15
|
+
language: "JA",
|
|
16
|
+
country: "JP"
|
|
17
|
+
});
|
|
18
|
+
expect(
|
|
19
|
+
getLocaleFromRequest(new Request("https://es.sub.example.com"))
|
|
20
|
+
).toMatchObject({
|
|
21
|
+
language: "ES",
|
|
22
|
+
country: "ES"
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getLocaleFromRequest } from './templates/subfolders.js';
|
|
3
|
+
|
|
4
|
+
describe("Setup i18n with subfolders", () => {
|
|
5
|
+
it("extracts the locale from the pathname", () => {
|
|
6
|
+
expect(
|
|
7
|
+
getLocaleFromRequest(new Request("https://example.com"))
|
|
8
|
+
).toMatchObject({
|
|
9
|
+
language: "EN",
|
|
10
|
+
country: "US"
|
|
11
|
+
});
|
|
12
|
+
expect(
|
|
13
|
+
getLocaleFromRequest(new Request("https://example.com/ja-jp"))
|
|
14
|
+
).toMatchObject({
|
|
15
|
+
language: "JA",
|
|
16
|
+
country: "JP"
|
|
17
|
+
});
|
|
18
|
+
expect(
|
|
19
|
+
getLocaleFromRequest(new Request("https://example.com/es-es/path"))
|
|
20
|
+
).toMatchObject({
|
|
21
|
+
language: "ES",
|
|
22
|
+
country: "ES"
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function getLocaleFromRequest(request) {
|
|
2
|
+
const defaultLocale = { language: "EN", country: "US" };
|
|
3
|
+
const supportedLocales = {
|
|
4
|
+
ES: "ES",
|
|
5
|
+
FR: "FR",
|
|
6
|
+
DE: "DE",
|
|
7
|
+
JP: "JA"
|
|
8
|
+
};
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const domain = url.hostname.split(".").pop()?.toUpperCase();
|
|
11
|
+
return supportedLocales[domain] ? { language: supportedLocales[domain], country: domain } : defaultLocale;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { getLocaleFromRequest };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {LanguageCode, CountryCode} from '../mock-i18n-types.js';
|
|
2
|
+
|
|
3
|
+
export type I18nLocale = {language: LanguageCode; country: CountryCode};
|
|
4
|
+
|
|
5
|
+
function getLocaleFromRequest(request: Request): I18nLocale {
|
|
6
|
+
const defaultLocale: I18nLocale = {language: 'EN', country: 'US'};
|
|
7
|
+
const supportedLocales = {
|
|
8
|
+
ES: 'ES',
|
|
9
|
+
FR: 'FR',
|
|
10
|
+
DE: 'DE',
|
|
11
|
+
JP: 'JA',
|
|
12
|
+
} as Record<CountryCode, LanguageCode>;
|
|
13
|
+
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
const domain = url.hostname
|
|
16
|
+
.split('.')
|
|
17
|
+
.pop()
|
|
18
|
+
?.toUpperCase() as keyof typeof supportedLocales;
|
|
19
|
+
|
|
20
|
+
return supportedLocales[domain]
|
|
21
|
+
? {language: supportedLocales[domain], country: domain}
|
|
22
|
+
: defaultLocale;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {getLocaleFromRequest};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function getLocaleFromRequest(request) {
|
|
2
|
+
const defaultLocale = { language: "EN", country: "US" };
|
|
3
|
+
const supportedLocales = {
|
|
4
|
+
ES: "ES",
|
|
5
|
+
FR: "FR",
|
|
6
|
+
DE: "DE",
|
|
7
|
+
JP: "JA"
|
|
8
|
+
};
|
|
9
|
+
const url = new URL(request.url);
|
|
10
|
+
const firstSubdomain = url.hostname.split(".")[0]?.toUpperCase();
|
|
11
|
+
return supportedLocales[firstSubdomain] ? { language: supportedLocales[firstSubdomain], country: firstSubdomain } : defaultLocale;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { getLocaleFromRequest };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type {LanguageCode, CountryCode} from '../mock-i18n-types.js';
|
|
2
|
+
|
|
3
|
+
export type I18nLocale = {language: LanguageCode; country: CountryCode};
|
|
4
|
+
|
|
5
|
+
function getLocaleFromRequest(request: Request): I18nLocale {
|
|
6
|
+
const defaultLocale: I18nLocale = {language: 'EN', country: 'US'};
|
|
7
|
+
const supportedLocales = {
|
|
8
|
+
ES: 'ES',
|
|
9
|
+
FR: 'FR',
|
|
10
|
+
DE: 'DE',
|
|
11
|
+
JP: 'JA',
|
|
12
|
+
} as Record<CountryCode, LanguageCode>;
|
|
13
|
+
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
const firstSubdomain = url.hostname
|
|
16
|
+
.split('.')[0]
|
|
17
|
+
?.toUpperCase() as keyof typeof supportedLocales;
|
|
18
|
+
|
|
19
|
+
return supportedLocales[firstSubdomain]
|
|
20
|
+
? {language: supportedLocales[firstSubdomain], country: firstSubdomain}
|
|
21
|
+
: defaultLocale;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {getLocaleFromRequest};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function getLocaleFromRequest(request) {
|
|
2
|
+
const url = new URL(request.url);
|
|
3
|
+
const firstPathPart = url.pathname.split("/")[1]?.toUpperCase() ?? "";
|
|
4
|
+
let pathPrefix = "";
|
|
5
|
+
let language = "EN";
|
|
6
|
+
let country = "US";
|
|
7
|
+
if (/^[A-Z]{2}-[A-Z]{2}$/i.test(firstPathPart)) {
|
|
8
|
+
pathPrefix = "/" + firstPathPart;
|
|
9
|
+
[language, country] = firstPathPart.split("-");
|
|
10
|
+
}
|
|
11
|
+
return { language, country, pathPrefix };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { getLocaleFromRequest };
|