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(dev/plugin): add localization to static pages #430

Merged
merged 15 commits into from
Nov 10, 2023
5 changes: 4 additions & 1 deletion packages/pages/src/common/src/feature/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface StaticPageConfig extends FeatureConfigBase {
staticPage: {
urlTemplate?: string;
htmlTemplate?: string;
locales?: string[];
};
}

Expand Down Expand Up @@ -85,7 +86,9 @@ export const convertTemplateConfigToFeatureConfig = (
if (isStaticTemplateConfig(config)) {
featureConfig = {
...featureConfigBase,
staticPage: {},
staticPage: {
locales: config.locales,
},
};
} else {
featureConfig = {
Expand Down
2 changes: 2 additions & 0 deletions packages/pages/src/common/src/template/internal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export interface TemplateConfigInternal {
alternateLanguageFields?: string[];
/** The name of the onUrlChange function to use. */
onUrlChange?: string;
/** Locales for a Static Page */
locales?: string[];
}

/**
Expand Down
16 changes: 16 additions & 0 deletions packages/pages/src/common/src/template/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ export interface TemplateConfig {
onUrlChange?: string;
}

/**
* Shape of TemplateConfig for Static Pages
*
* @public
*/
export interface StaticTemplateConfig {
alextaing marked this conversation as resolved.
Show resolved Hide resolved
/** The name of the template feature. If not defined uses the template filename (without extension) */
name?: string;
/** Determines if hydration is allowed or not for webpages */
hydrate?: boolean;
/** The name of the onUrlChange function to use. */
onUrlChange?: string;
/** Locales for a static page */
locales?: string[];
}

/**
* The stream config defined in {@link TemplateConfig.stream}.
*
Expand Down
24 changes: 12 additions & 12 deletions packages/pages/src/dev/server/middleware/indexPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,40 +192,40 @@ const createStaticPageListItems = (
devServerPort: number
) => {
return Array.from(localDataManifest.static).reduce(
(templateAccumulator, [, { featureName, staticURL, locales }]) =>
(templateAccumulator, [, { featureName, pathToLocaleMap }]) =>
templateAccumulator +
`<h4>${featureName} pages (${locales.length}):</h4>` +
`<h4>${featureName} pages (${pathToLocaleMap.size}):</h4>` +
`<table>
<thead>
<tr>
<td>URL</td>
${locales.length > 1 ? "<td>Locale</td>" : ""}
${pathToLocaleMap.size > 1 ? "<td>Locale</td>" : ""}
</tr>
</thead>
<tbody>
${locales
${[...pathToLocaleMap]
.map(
(locale) => `<tr>
([path, locale]) => `<tr>
${
locales.length > 1
pathToLocaleMap.size > 1
? `<td>
<a href="http://localhost:${devServerPort}/${staticURL}?locale=${locale}">
${staticURL}?locale=${locale}
<a href="http://localhost:${devServerPort}/${path}?locale=${locale}">
${path}?locale=${locale}
</a>
</td>`
: `<td>
<a href="http://localhost:${devServerPort}/${staticURL}">
${staticURL}
<a href="http://localhost:${devServerPort}/${path}">
${path}
</a>
</td>`
}
${locales.length > 1 ? `<td>${locale}</td>` : ""}`
${pathToLocaleMap.size > 1 ? `<td>${locale}</td>` : ""}`
)
.join("")}
</tr>
</tbody>
</table>
`,
`,
""
);
};
Expand Down
16 changes: 10 additions & 6 deletions packages/pages/src/dev/server/ssr/findMatchingStaticTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@ export default async function findMatchingStaticTemplate(
if (!isStaticTemplateConfig(t.config)) {
return false;
}
const document = await getLocalDataForEntityOrStaticPage({
entityId: "",
locale,
featureName: t.config.name,
});
return slug === t.getPath({ document });
try {
alextaing marked this conversation as resolved.
Show resolved Hide resolved
const document = await getLocalDataForEntityOrStaticPage({
entityId: "",
locale,
featureName: t.config.name,
});
return slug === t.getPath({ document });
} catch {
return false;
}
},
templateFilepaths
);
Expand Down
32 changes: 19 additions & 13 deletions packages/pages/src/dev/server/ssr/getLocalData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ export interface LocalDataManifest {
{
// The featureName for a specific static template
featureName: string;
// The return value of the template's getPath()
staticURL: string;
// The locale codes
locales: string[];
// A map from static page path to a single locale
pathToLocaleMap: Map<string, string>;
}
>;
entity: Map<
Expand Down Expand Up @@ -97,27 +95,35 @@ export const getLocalDataManifest = async (
);
continue;
}
if (localDataManifest.static.has(featureName)) {
localDataManifest.static
.get(featureName)
?.locales.push(data.meta.locale);

const staticPath = templateModuleInternal.getPath({ document: data });
const currentManifestData = localDataManifest.static.get(featureName);
if (currentManifestData) {
const occupiedPaths = currentManifestData.pathToLocaleMap;
if (occupiedPaths.has(staticPath)) {
throw new Error(
`Path "${staticPath}" is used by multiple static pages. Check that ` +
`the getPath() function in the template "${templateModuleInternal.templateName}" ` +
"returns a unique path for each locale."
);
}
occupiedPaths.set(staticPath, data.meta.locale);
} else {
const staticURL = templateModuleInternal.getPath({ document: data });
try {
validateGetPathValue(staticURL, templateModuleInternal.path);
validateGetPathValue(staticPath, templateModuleInternal.path);
} catch (e) {
logWarning(`${(e as Error).message}, skipping."`);
continue;
}
const pathToLocaleMap = new Map();
pathToLocaleMap.set(staticPath, data.meta.locale);
localDataManifest.static.set(featureName, {
featureName,
staticURL,
locales: [data.meta.locale],
pathToLocaleMap,
});
}
}
}

return localDataManifest;
};

Expand Down
34 changes: 34 additions & 0 deletions packages/pages/src/vite-plugin/build/closeBundle/closeBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { logErrorAndClean } from "../../../util/logError.js";
import { isUsingConfig } from "../../../util/config.js";
import { createArtifactsJson } from "../../../generate/artifacts/createArtifactsJson.js";
import { Path } from "../../../common/src/project/path.js";
import { getLocalDataForEntityOrStaticPage } from "../../../dev/server/ssr/getLocalData.js";

export default (projectStructure: ProjectStructure) => {
return async () => {
Expand Down Expand Up @@ -61,6 +62,7 @@ export default (projectStructure: ProjectStructure) => {
);

validateUniqueFeatureName(templateModules);
await validateUniqueStaticPaths(templateModules);
validateBundles(projectStructure);
finisher.succeed("Validated template modules");
} catch (e: any) {
Expand Down Expand Up @@ -232,3 +234,35 @@ const validateUniqueFeatureName = (
featureNames.add(featureName);
});
};

const validateUniqueStaticPaths = (
templateModuleCollection: TemplateModuleCollection
) => {
const paths = new Set<string>();
const pathPromises = [...templateModuleCollection.values()].map(
async (module) => {
if (!module.config.locales) {
return;
}

for (const locale of module.config.locales) {
const document = await getLocalDataForEntityOrStaticPage({
entityId: "",
locale,
featureName: module.config.name,
});
const path = module.getPath({ document });
if (paths.has(path)) {
throw (
`Path "${path}" is used by multiple static pages. Check that ` +
`the getPath() function in the template "${module.templateName}" ` +
"returns a unique path for each locale."
);
} else {
paths.add(path);
}
}
}
);
return Promise.all(pathPromises);
};