@bleedingdev/modern-js-create 3.2.0-ultramodern.120 → 3.2.0-ultramodern.121
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/README.md +35 -12
- package/dist/cjs/create-package-root.cjs +7 -9
- package/dist/cjs/index.cjs +74 -44
- package/dist/cjs/locale/en.cjs +6 -7
- package/dist/cjs/locale/zh.cjs +6 -7
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +337 -0
- package/dist/cjs/ultramodern-workspace/app-files.cjs +223 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +836 -0
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +422 -0
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +222 -0
- package/dist/cjs/ultramodern-workspace/effect-api.cjs +952 -0
- package/dist/cjs/ultramodern-workspace/fs-io.cjs +191 -0
- package/dist/cjs/ultramodern-workspace/index.cjs +48 -0
- package/dist/cjs/ultramodern-workspace/locales.cjs +173 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +487 -0
- package/dist/cjs/ultramodern-workspace/naming.cjs +161 -0
- package/dist/cjs/ultramodern-workspace/package-json.cjs +406 -0
- package/dist/cjs/ultramodern-workspace/package-source.cjs +59 -0
- package/dist/cjs/ultramodern-workspace/policy.cjs +248 -0
- package/dist/cjs/ultramodern-workspace/public-surface.cjs +268 -0
- package/dist/cjs/ultramodern-workspace/routes.cjs +375 -0
- package/dist/cjs/ultramodern-workspace/types.cjs +61 -0
- package/dist/cjs/ultramodern-workspace/versions.cjs +153 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +153 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +175 -0
- package/dist/esm/create-package-root.js +7 -9
- package/dist/esm/index.js +72 -42
- package/dist/esm/locale/en.js +6 -7
- package/dist/esm/locale/zh.js +6 -7
- package/dist/esm/ultramodern-workspace/add-vertical.js +252 -0
- package/dist/esm/ultramodern-workspace/app-files.js +149 -0
- package/dist/esm/ultramodern-workspace/contracts.js +741 -0
- package/dist/esm/ultramodern-workspace/demo-components.js +363 -0
- package/dist/esm/ultramodern-workspace/descriptors.js +133 -0
- package/dist/esm/ultramodern-workspace/effect-api.js +854 -0
- package/dist/esm/ultramodern-workspace/fs-io.js +90 -0
- package/dist/esm/ultramodern-workspace/index.js +3 -0
- package/dist/esm/ultramodern-workspace/locales.js +122 -0
- package/dist/esm/ultramodern-workspace/module-federation.js +415 -0
- package/dist/esm/ultramodern-workspace/naming.js +71 -0
- package/dist/esm/ultramodern-workspace/package-json.js +338 -0
- package/dist/esm/ultramodern-workspace/package-source.js +21 -0
- package/dist/esm/ultramodern-workspace/policy.js +183 -0
- package/dist/esm/ultramodern-workspace/public-surface.js +183 -0
- package/dist/esm/ultramodern-workspace/routes.js +280 -0
- package/dist/esm/ultramodern-workspace/types.js +16 -0
- package/dist/esm/ultramodern-workspace/versions.js +34 -0
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +91 -0
- package/dist/esm/ultramodern-workspace/write-workspace.js +121 -0
- package/dist/esm-node/create-package-root.js +7 -9
- package/dist/esm-node/index.js +72 -42
- package/dist/esm-node/locale/en.js +6 -7
- package/dist/esm-node/locale/zh.js +6 -7
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +253 -0
- package/dist/esm-node/ultramodern-workspace/app-files.js +150 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +742 -0
- package/dist/esm-node/ultramodern-workspace/demo-components.js +364 -0
- package/dist/esm-node/ultramodern-workspace/descriptors.js +134 -0
- package/dist/esm-node/ultramodern-workspace/effect-api.js +855 -0
- package/dist/esm-node/ultramodern-workspace/fs-io.js +91 -0
- package/dist/esm-node/ultramodern-workspace/index.js +4 -0
- package/dist/esm-node/ultramodern-workspace/locales.js +123 -0
- package/dist/esm-node/ultramodern-workspace/module-federation.js +416 -0
- package/dist/esm-node/ultramodern-workspace/naming.js +72 -0
- package/dist/esm-node/ultramodern-workspace/package-json.js +339 -0
- package/dist/esm-node/ultramodern-workspace/package-source.js +22 -0
- package/dist/esm-node/ultramodern-workspace/policy.js +184 -0
- package/dist/esm-node/ultramodern-workspace/public-surface.js +184 -0
- package/dist/esm-node/ultramodern-workspace/routes.js +281 -0
- package/dist/esm-node/ultramodern-workspace/types.js +17 -0
- package/dist/esm-node/ultramodern-workspace/versions.js +35 -0
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +92 -0
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +122 -0
- package/dist/types/locale/en.d.ts +4 -5
- package/dist/types/locale/index.d.ts +8 -10
- package/dist/types/locale/zh.d.ts +4 -5
- package/dist/types/ultramodern-workspace/add-vertical.d.ts +19 -0
- package/dist/types/ultramodern-workspace/app-files.d.ts +14 -0
- package/dist/types/ultramodern-workspace/contracts.d.ts +21 -0
- package/dist/types/ultramodern-workspace/demo-components.d.ts +9 -0
- package/dist/types/ultramodern-workspace/descriptors.d.ts +39 -0
- package/dist/types/ultramodern-workspace/effect-api.d.ts +73 -0
- package/dist/types/ultramodern-workspace/fs-io.d.ts +18 -0
- package/dist/types/ultramodern-workspace/index.d.ts +4 -0
- package/dist/types/ultramodern-workspace/locales.d.ts +183 -0
- package/dist/types/ultramodern-workspace/module-federation.d.ts +16 -0
- package/dist/types/ultramodern-workspace/naming.d.ts +16 -0
- package/dist/types/ultramodern-workspace/package-json.d.ts +12 -0
- package/dist/types/ultramodern-workspace/package-source.d.ts +2 -0
- package/dist/types/ultramodern-workspace/policy.d.ts +60 -0
- package/dist/types/ultramodern-workspace/public-surface.d.ts +37 -0
- package/dist/types/ultramodern-workspace/routes.d.ts +25 -0
- package/dist/types/ultramodern-workspace/types.d.ts +95 -0
- package/dist/types/ultramodern-workspace/versions.d.ts +38 -0
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +10 -0
- package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -0
- package/package.json +4 -3
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +1 -4
- package/template-workspace/.mise.toml.handlebars +1 -0
- package/template-workspace/{AGENTS.md → AGENTS.md.handlebars} +12 -7
- package/template-workspace/README.md.handlebars +40 -24
- package/template-workspace/{pnpm-workspace.yaml → pnpm-workspace.yaml.handlebars} +2 -2
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +31 -51
- package/templates/app/shell-frame.tsx +49 -0
- package/templates/app/ultramodern-route-head.tsx.handlebars +142 -0
- package/templates/packages/shared-contracts-index.ts +466 -0
- package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +69 -0
- package/templates/workspace-scripts/check-ultramodern-i18n-boundaries.mjs +9 -0
- package/templates/workspace-scripts/generate-public-surface-assets.mjs +529 -0
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +125 -0
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +851 -0
- package/templates/workspace-scripts/ultramodern-performance-readiness.config.mjs +7 -0
- package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +223 -0
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +593 -0
- package/dist/cjs/ultramodern-workspace.cjs +0 -6797
- package/dist/esm/ultramodern-workspace.js +0 -6738
- package/dist/esm-node/ultramodern-workspace.js +0 -6739
- package/dist/types/ultramodern-workspace.d.ts +0 -29
|
@@ -37,45 +37,14 @@ const commandExists = command => {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
const
|
|
41
|
-
run('sh', ['-lc', script], {
|
|
42
|
-
stdio: 'inherit',
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const installGit = () => {
|
|
40
|
+
const requireGit = () => {
|
|
46
41
|
if (commandExists('git')) {
|
|
47
42
|
return;
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const sudo =
|
|
54
|
-
typeof process.getuid === 'function' && process.getuid() === 0
|
|
55
|
-
? ''
|
|
56
|
-
: 'sudo ';
|
|
57
|
-
runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
|
|
58
|
-
} else if (process.platform === 'linux' && commandExists('dnf')) {
|
|
59
|
-
const sudo =
|
|
60
|
-
typeof process.getuid === 'function' && process.getuid() === 0
|
|
61
|
-
? ''
|
|
62
|
-
: 'sudo ';
|
|
63
|
-
runShell(`${sudo}dnf install -y git`);
|
|
64
|
-
} else if (process.platform === 'linux' && commandExists('yum')) {
|
|
65
|
-
const sudo =
|
|
66
|
-
typeof process.getuid === 'function' && process.getuid() === 0
|
|
67
|
-
? ''
|
|
68
|
-
: 'sudo ';
|
|
69
|
-
runShell(`${sudo}yum install -y git`);
|
|
70
|
-
} else if (process.platform === 'linux' && commandExists('apk')) {
|
|
71
|
-
runShell('apk add --no-cache git');
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!commandExists('git')) {
|
|
75
|
-
throw new Error(
|
|
76
|
-
'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
|
|
77
|
-
);
|
|
78
|
-
}
|
|
45
|
+
throw new Error(
|
|
46
|
+
'Git is required to install agent skills. Install git yourself (for example "brew install git" or "sudo apt-get install git") and run pnpm skills:install again. This script never installs system packages on your behalf.',
|
|
47
|
+
);
|
|
79
48
|
};
|
|
80
49
|
|
|
81
50
|
const isInsideGitWorkTree = () => {
|
|
@@ -183,13 +152,15 @@ const requiredCloneSources = sources.filter(
|
|
|
183
152
|
const optionalCloneSources = sources.filter(
|
|
184
153
|
source => source.install === 'clone-if-authorized',
|
|
185
154
|
);
|
|
186
|
-
const
|
|
187
|
-
...(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
(skill, index, skills) =>
|
|
191
|
-
skills.findIndex(candidate => candidate.name === skill.name) === index,
|
|
155
|
+
const cloneSourceSkillNames = new Set(
|
|
156
|
+
[...requiredCloneSources, ...optionalCloneSources].flatMap(source =>
|
|
157
|
+
(source.baseline ?? []).map(skill => skill.name),
|
|
158
|
+
),
|
|
192
159
|
);
|
|
160
|
+
const vendoredRequiredSkills = (lock.baseline ?? []).filter(
|
|
161
|
+
skill => !cloneSourceSkillNames.has(skill.name),
|
|
162
|
+
);
|
|
163
|
+
const cloneOptIn = truthy(process.env.ULTRAMODERN_AGENT_SKILLS);
|
|
193
164
|
|
|
194
165
|
if (skipRequested) {
|
|
195
166
|
const reason = 'agent skills bootstrap skipped by environment';
|
|
@@ -203,12 +174,15 @@ if (skipRequested) {
|
|
|
203
174
|
}
|
|
204
175
|
|
|
205
176
|
if (checkOnly) {
|
|
206
|
-
const
|
|
177
|
+
const missingVendored = vendoredRequiredSkills
|
|
207
178
|
.map(skill => skill.name)
|
|
208
179
|
.filter(
|
|
209
180
|
skillName => !fs.existsSync(path.join(installDir, skillName, 'SKILL.md')),
|
|
210
181
|
);
|
|
211
|
-
const
|
|
182
|
+
const missingCloneInstalled = [
|
|
183
|
+
...requiredCloneSources,
|
|
184
|
+
...optionalCloneSources,
|
|
185
|
+
].flatMap(source =>
|
|
212
186
|
(source.baseline ?? [])
|
|
213
187
|
.map(skill => skill.name)
|
|
214
188
|
.filter(
|
|
@@ -217,27 +191,33 @@ if (checkOnly) {
|
|
|
217
191
|
),
|
|
218
192
|
);
|
|
219
193
|
|
|
220
|
-
if (
|
|
194
|
+
if (missingVendored.length > 0) {
|
|
221
195
|
console.error(
|
|
222
|
-
`Required agent skills not installed: ${
|
|
196
|
+
`Required agent skills not installed: ${missingVendored.join(', ')}. Run pnpm skills:install.`,
|
|
223
197
|
);
|
|
224
198
|
process.exit(1);
|
|
225
199
|
}
|
|
226
200
|
|
|
227
|
-
if (
|
|
201
|
+
if (missingCloneInstalled.length > 0) {
|
|
228
202
|
console.warn(
|
|
229
|
-
`
|
|
203
|
+
`Clone-installed agent skills not present: ${missingCloneInstalled.join(', ')}. Run pnpm skills:install to fetch them.`,
|
|
230
204
|
);
|
|
231
205
|
} else {
|
|
232
|
-
console.log('
|
|
233
|
-
process.exit(0);
|
|
206
|
+
console.log('All pinned agent skills are installed.');
|
|
234
207
|
}
|
|
235
|
-
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (postinstall && !cloneOptIn) {
|
|
212
|
+
console.log(
|
|
213
|
+
'Skipping agent skill repository clones during postinstall. Run pnpm skills:install (or set ULTRAMODERN_AGENT_SKILLS=1 before installing) to fetch them.',
|
|
214
|
+
);
|
|
215
|
+
installLefthook();
|
|
236
216
|
process.exit(0);
|
|
237
217
|
}
|
|
238
218
|
|
|
239
219
|
fs.mkdirSync(installDir, { recursive: true });
|
|
240
|
-
|
|
220
|
+
requireGit();
|
|
241
221
|
initializeGitRepository();
|
|
242
222
|
|
|
243
223
|
for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useLocalizedLocation, useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { Header, StatusBadge } from './vertical-components';
|
|
4
|
+
|
|
5
|
+
interface ShellFrameProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function ShellFrame({ children }: ShellFrameProps) {
|
|
10
|
+
const { i18nInstance, language } = useModernI18n();
|
|
11
|
+
const t = i18nInstance['t'].bind(i18nInstance);
|
|
12
|
+
const { alternates } = useLocalizedLocation();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<main className="shell:min-h-screen shell:bg-um-canvas shell:px-4 shell:py-5 shell:text-um-foreground shell:sm:px-6 shell:lg:px-12">
|
|
16
|
+
<div className="shell:mx-auto shell:flex shell:min-h-20 shell:max-w-7xl shell:flex-col shell:items-start shell:gap-3 shell:bg-white/90 shell:px-4 shell:py-3 shell:shadow-xl shell:shadow-stone-900/10 shell:sm:px-6 shell:md:flex-row shell:md:flex-wrap shell:md:items-center shell:md:justify-between">
|
|
17
|
+
<Header />
|
|
18
|
+
<div className="shell:flex shell:min-w-0 shell:flex-wrap shell:items-center shell:gap-2 shell:md:ml-auto">
|
|
19
|
+
<label className="shell:sr-only" htmlFor="ultramodern-language">
|
|
20
|
+
{t('shell.language.switcher')}
|
|
21
|
+
</label>
|
|
22
|
+
<select
|
|
23
|
+
aria-label={t('shell.language.switcher')}
|
|
24
|
+
className="shell:h-10 shell:w-10 shell:cursor-pointer shell:appearance-none shell:border-0 shell:bg-transparent shell:p-0 shell:text-center shell:text-3xl shell:font-black shell:leading-none shell:text-stone-950 shell:shadow-none shell:[appearance:none] shell:[text-align-last:center] shell:focus-visible:rounded-md shell:focus-visible:outline-3 shell:focus-visible:outline-offset-2 shell:focus-visible:outline-emerald-700/40 shell:[&::-ms-expand]:hidden shell:[&::picker-icon]:hidden shell:[&_option]:text-xl"
|
|
25
|
+
id="ultramodern-language"
|
|
26
|
+
name="language"
|
|
27
|
+
onChange={event => {
|
|
28
|
+
const nextLanguage = event.currentTarget.value;
|
|
29
|
+
const targetHref = alternates[nextLanguage];
|
|
30
|
+
if (targetHref !== undefined) {
|
|
31
|
+
window.location.assign(targetHref);
|
|
32
|
+
}
|
|
33
|
+
}}
|
|
34
|
+
value={language}
|
|
35
|
+
>
|
|
36
|
+
<option aria-label={t('shell.language.en')} value="en">
|
|
37
|
+
🇬🇧
|
|
38
|
+
</option>
|
|
39
|
+
<option aria-label={t('shell.language.cs')} value="cs">
|
|
40
|
+
🇨🇿
|
|
41
|
+
</option>
|
|
42
|
+
</select>
|
|
43
|
+
<StatusBadge />
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
{children}
|
|
47
|
+
</main>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { useLocalizedLocation, useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2
|
+
import { Helmet } from '@modern-js/runtime/head';
|
|
3
|
+
import {
|
|
4
|
+
ultramodernRouteMetadata,
|
|
5
|
+
} from './ultramodern-route-metadata';
|
|
6
|
+
import type { RouteJsonLd } from './ultramodern-jsonld';
|
|
7
|
+
|
|
8
|
+
const appName = {{appDisplayNameJson}};
|
|
9
|
+
const fallbackLanguage = 'en';
|
|
10
|
+
const supportedLanguages = ['en', 'cs'] as const;
|
|
11
|
+
type SupportedLanguage = (typeof supportedLanguages)[number];
|
|
12
|
+
type RouteMetadata = (typeof ultramodernRouteMetadata)[number] & {
|
|
13
|
+
readonly jsonLd?: RouteJsonLd;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const routeMetadata = ultramodernRouteMetadata as readonly RouteMetadata[];
|
|
17
|
+
|
|
18
|
+
const isSupportedLanguage = (value: string): value is SupportedLanguage =>
|
|
19
|
+
supportedLanguages.includes(value as SupportedLanguage);
|
|
20
|
+
|
|
21
|
+
const normalisePath = (pathname: string) => {
|
|
22
|
+
const normalised = pathname.replaceAll(/\/+/gu, '/').replace(/\/+$/u, '');
|
|
23
|
+
return normalised.length > 0 ? normalised : '/';
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const stripLanguagePrefix = (pathname: string) => {
|
|
27
|
+
const segments = normalisePath(pathname).split('/').filter(Boolean);
|
|
28
|
+
if (segments.length > 0 && isSupportedLanguage(segments[0] ?? '')) {
|
|
29
|
+
segments.shift();
|
|
30
|
+
}
|
|
31
|
+
return `/${segments.join('/')}`;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const escapeRegExp = (value: string) =>
|
|
35
|
+
value.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
36
|
+
|
|
37
|
+
const paramName = (segment: string) => segment.slice(1).replace(/\?$/u, '');
|
|
38
|
+
|
|
39
|
+
const matchPattern = (pathname: string, pattern: string) => {
|
|
40
|
+
const names: string[] = [];
|
|
41
|
+
const source = normalisePath(pattern)
|
|
42
|
+
.split('/')
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map(segment => {
|
|
45
|
+
if (segment.startsWith(':')) {
|
|
46
|
+
names.push(paramName(segment));
|
|
47
|
+
return segment.endsWith('?') ? '(?:/([^/]+))?' : '/([^/]+)';
|
|
48
|
+
}
|
|
49
|
+
return `/${escapeRegExp(segment)}`;
|
|
50
|
+
})
|
|
51
|
+
.join('');
|
|
52
|
+
const match = new RegExp(`^${source || '/'}$`, 'u').exec(
|
|
53
|
+
normalisePath(pathname),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
if (match === null) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const params: Record<string, string> = {};
|
|
61
|
+
for (const [index, name] of names.entries()) {
|
|
62
|
+
params[name] = decodeURIComponent(match[index + 1] ?? '');
|
|
63
|
+
}
|
|
64
|
+
return params;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const resolveRouteMetadata = (pathname: string) => {
|
|
68
|
+
const pathWithoutLanguage = stripLanguagePrefix(pathname);
|
|
69
|
+
|
|
70
|
+
for (const route of routeMetadata) {
|
|
71
|
+
const canonicalParams = matchPattern(pathWithoutLanguage, route.canonicalPath);
|
|
72
|
+
if (canonicalParams !== undefined) {
|
|
73
|
+
return route;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const language of supportedLanguages) {
|
|
77
|
+
const params = matchPattern(pathWithoutLanguage, route.localisedPaths[language]);
|
|
78
|
+
if (params !== undefined) {
|
|
79
|
+
return route;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return routeMetadata[0];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const absoluteUrl = (pathname: string) => {
|
|
88
|
+
const origin = ULTRAMODERN_SITE_URL.replace(/\/+$/u, '');
|
|
89
|
+
return `${origin}${pathname}`;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const sanitiseJsonLd = (value: RouteJsonLd) =>
|
|
93
|
+
JSON.stringify(value).replaceAll('<', '\\u003c');
|
|
94
|
+
|
|
95
|
+
export const UltramodernRouteHead = () => {
|
|
96
|
+
const { i18nInstance } = useModernI18n();
|
|
97
|
+
const t = i18nInstance['t'].bind(i18nInstance);
|
|
98
|
+
const { canonical, alternates } = useLocalizedLocation();
|
|
99
|
+
const route = resolveRouteMetadata(canonical);
|
|
100
|
+
const title = route ? t(route.titleKey) : appName;
|
|
101
|
+
const description = route ? t(route.descriptionKey) : appName;
|
|
102
|
+
const canonicalUrl = absoluteUrl(alternates[fallbackLanguage] ?? `/${fallbackLanguage}`);
|
|
103
|
+
const indexable = route?.public === true && route?.indexable === true;
|
|
104
|
+
const jsonLd = indexable ? route?.jsonLd : undefined;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Helmet htmlAttributes={{ lang: i18nInstance.language ?? fallbackLanguage }}>
|
|
108
|
+
<title>{title}</title>
|
|
109
|
+
<meta content={description} name="description" />
|
|
110
|
+
<meta content={indexable ? 'index, follow' : 'noindex, nofollow'} name="robots" />
|
|
111
|
+
{indexable && (
|
|
112
|
+
<>
|
|
113
|
+
<link rel="canonical" href={canonicalUrl} />
|
|
114
|
+
{supportedLanguages.map(code => (
|
|
115
|
+
<link
|
|
116
|
+
href={absoluteUrl(alternates[code] ?? `/${code}`)}
|
|
117
|
+
hrefLang={code}
|
|
118
|
+
key={code}
|
|
119
|
+
rel="alternate"
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
<link
|
|
123
|
+
href={absoluteUrl(alternates[fallbackLanguage] ?? `/${fallbackLanguage}`)}
|
|
124
|
+
hrefLang="x-default"
|
|
125
|
+
rel="alternate"
|
|
126
|
+
/>
|
|
127
|
+
<meta content={title} property="og:title" />
|
|
128
|
+
<meta content={description} property="og:description" />
|
|
129
|
+
<meta content={canonicalUrl} property="og:url" />
|
|
130
|
+
<meta content="website" property="og:type" />
|
|
131
|
+
<meta content={i18nInstance.language ?? fallbackLanguage} property="og:locale" />
|
|
132
|
+
<meta content="summary_large_image" name="twitter:card" />
|
|
133
|
+
<meta content={title} name="twitter:title" />
|
|
134
|
+
<meta content={description} name="twitter:description" />
|
|
135
|
+
{jsonLd && (
|
|
136
|
+
<script type="application/ld+json">{sanitiseJsonLd(jsonLd)}</script>
|
|
137
|
+
)}
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
</Helmet>
|
|
141
|
+
);
|
|
142
|
+
};
|