mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-29 18:27:56 +02:00
fix(core): all plugin lifecycles should receive translated content (#7066)
This commit is contained in:
parent
3f33e90704
commit
fd24bd180d
3 changed files with 77 additions and 112 deletions
|
@ -125,10 +125,7 @@ export async function load(options: LoadContextOptions): Promise<Props> {
|
||||||
ssrTemplate,
|
ssrTemplate,
|
||||||
codeTranslations: siteCodeTranslations,
|
codeTranslations: siteCodeTranslations,
|
||||||
} = context;
|
} = context;
|
||||||
const {plugins, pluginsRouteConfigs, globalData, themeConfigTranslated} =
|
const {plugins, pluginsRouteConfigs, globalData} = await loadPlugins(context);
|
||||||
await loadPlugins(context);
|
|
||||||
// Side-effect to replace the untranslated themeConfig by the translated one
|
|
||||||
context.siteConfig.themeConfig = themeConfigTranslated;
|
|
||||||
const clientModules = loadClientModules(plugins);
|
const clientModules = loadClientModules(plugins);
|
||||||
const {headTags, preBodyTags, postBodyTags} = loadHtmlTags(plugins);
|
const {headTags, preBodyTags, postBodyTags} = loadHtmlTags(plugins);
|
||||||
const {registry, routesChunkNames, routesConfig, routesPaths} =
|
const {registry, routesChunkNames, routesConfig, routesPaths} =
|
||||||
|
|
|
@ -64,6 +64,5 @@ exports[`loadPlugins loads plugins 1`] = `
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"pluginsRouteConfigs": [],
|
"pluginsRouteConfigs": [],
|
||||||
"themeConfigTranslated": {},
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -13,7 +13,6 @@ import type {
|
||||||
RouteConfig,
|
RouteConfig,
|
||||||
AllContent,
|
AllContent,
|
||||||
GlobalData,
|
GlobalData,
|
||||||
ThemeConfig,
|
|
||||||
LoadedPlugin,
|
LoadedPlugin,
|
||||||
InitializedPlugin,
|
InitializedPlugin,
|
||||||
PluginRouteContext,
|
PluginRouteContext,
|
||||||
|
@ -28,13 +27,13 @@ import {applyRouteTrailingSlash, sortConfig} from './routeConfig';
|
||||||
/**
|
/**
|
||||||
* Initializes the plugins, runs `loadContent`, `translateContent`,
|
* Initializes the plugins, runs `loadContent`, `translateContent`,
|
||||||
* `contentLoaded`, and `translateThemeConfig`. Because `contentLoaded` is
|
* `contentLoaded`, and `translateThemeConfig`. Because `contentLoaded` is
|
||||||
* side-effect-ful (it generates temp files), so is this function.
|
* side-effect-ful (it generates temp files), so is this function. This function
|
||||||
|
* would also mutate `context.siteConfig.themeConfig` to translate it.
|
||||||
*/
|
*/
|
||||||
export async function loadPlugins(context: LoadContext): Promise<{
|
export async function loadPlugins(context: LoadContext): Promise<{
|
||||||
plugins: LoadedPlugin[];
|
plugins: LoadedPlugin[];
|
||||||
pluginsRouteConfigs: RouteConfig[];
|
pluginsRouteConfigs: RouteConfig[];
|
||||||
globalData: GlobalData;
|
globalData: GlobalData;
|
||||||
themeConfigTranslated: ThemeConfig;
|
|
||||||
}> {
|
}> {
|
||||||
// 1. Plugin Lifecycle - Initialization/Constructor.
|
// 1. Plugin Lifecycle - Initialization/Constructor.
|
||||||
const plugins: InitializedPlugin[] = await initPlugins(context);
|
const plugins: InitializedPlugin[] = await initPlugins(context);
|
||||||
|
@ -48,21 +47,15 @@ export async function loadPlugins(context: LoadContext): Promise<{
|
||||||
// Currently plugins run lifecycle methods in parallel and are not
|
// Currently plugins run lifecycle methods in parallel and are not
|
||||||
// order-dependent. We could change this in future if there are plugins which
|
// order-dependent. We could change this in future if there are plugins which
|
||||||
// need to run in certain order or depend on others for data.
|
// need to run in certain order or depend on others for data.
|
||||||
|
// This would also translate theme config and content upfront, given the
|
||||||
|
// translation files that the plugin declares.
|
||||||
const loadedPlugins: LoadedPlugin[] = await Promise.all(
|
const loadedPlugins: LoadedPlugin[] = await Promise.all(
|
||||||
plugins.map(async (plugin) => {
|
plugins.map(async (plugin) => {
|
||||||
const content = await plugin.loadContent?.();
|
const content = await plugin.loadContent?.();
|
||||||
return {...plugin, content};
|
const rawTranslationFiles =
|
||||||
}),
|
(await plugin?.getTranslationFiles?.({content})) ?? [];
|
||||||
);
|
const translationFiles = await Promise.all(
|
||||||
|
rawTranslationFiles.map((translationFile) =>
|
||||||
const contentLoadedTranslatedPlugins = await Promise.all(
|
|
||||||
loadedPlugins.map(async (plugin) => {
|
|
||||||
const translationFiles =
|
|
||||||
(await plugin?.getTranslationFiles?.({
|
|
||||||
content: plugin.content,
|
|
||||||
})) ?? [];
|
|
||||||
const localizedTranslationFiles = await Promise.all(
|
|
||||||
translationFiles.map((translationFile) =>
|
|
||||||
localizePluginTranslationFile({
|
localizePluginTranslationFile({
|
||||||
locale: context.i18n.currentLocale,
|
locale: context.i18n.currentLocale,
|
||||||
siteDir: context.siteDir,
|
siteDir: context.siteDir,
|
||||||
|
@ -71,10 +64,17 @@ export async function loadPlugins(context: LoadContext): Promise<{
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return {
|
const translatedContent =
|
||||||
...plugin,
|
plugin.translateContent?.({content, translationFiles}) ?? content;
|
||||||
translationFiles: localizedTranslationFiles,
|
const translatedThemeConfigSlice = plugin.translateThemeConfig?.({
|
||||||
};
|
themeConfig: context.siteConfig.themeConfig,
|
||||||
|
translationFiles,
|
||||||
|
});
|
||||||
|
// Side-effect to merge theme config translations. A plugin should only
|
||||||
|
// translate its own slice of theme config and should make no assumptions
|
||||||
|
// about other plugins' keys, so this is safe to run in parallel.
|
||||||
|
Object.assign(context.siteConfig.themeConfig, translatedThemeConfigSlice);
|
||||||
|
return {...plugin, content: translatedContent};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -90,86 +90,75 @@ export async function loadPlugins(context: LoadContext): Promise<{
|
||||||
|
|
||||||
// 3. Plugin Lifecycle - contentLoaded.
|
// 3. Plugin Lifecycle - contentLoaded.
|
||||||
const pluginsRouteConfigs: RouteConfig[] = [];
|
const pluginsRouteConfigs: RouteConfig[] = [];
|
||||||
|
|
||||||
const globalData: GlobalData = {};
|
const globalData: GlobalData = {};
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
contentLoadedTranslatedPlugins.map(
|
loadedPlugins.map(async ({content, ...plugin}) => {
|
||||||
async ({content, translationFiles, ...plugin}) => {
|
if (!plugin.contentLoaded) {
|
||||||
if (!plugin.contentLoaded) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
const pluginId = plugin.options.id;
|
||||||
const pluginId = plugin.options.id;
|
// plugins data files are namespaced by pluginName/pluginId
|
||||||
// plugins data files are namespaced by pluginName/pluginId
|
const dataDir = path.join(
|
||||||
const dataDir = path.join(
|
context.generatedFilesDir,
|
||||||
context.generatedFilesDir,
|
plugin.name,
|
||||||
plugin.name,
|
pluginId,
|
||||||
pluginId,
|
);
|
||||||
);
|
// TODO this would be better to do all that in the codegen phase
|
||||||
// TODO this would be better to do all that in the codegen phase
|
// TODO handle context for nested routes
|
||||||
// TODO handle context for nested routes
|
const pluginRouteContext: PluginRouteContext = {
|
||||||
const pluginRouteContext: PluginRouteContext = {
|
plugin: {name: plugin.name, id: pluginId},
|
||||||
plugin: {name: plugin.name, id: pluginId},
|
data: undefined, // TODO allow plugins to provide context data
|
||||||
data: undefined, // TODO allow plugins to provide context data
|
};
|
||||||
};
|
const pluginRouteContextModulePath = path.join(
|
||||||
const pluginRouteContextModulePath = path.join(
|
dataDir,
|
||||||
dataDir,
|
`${docuHash('pluginRouteContextModule')}.json`,
|
||||||
`${docuHash('pluginRouteContextModule')}.json`,
|
);
|
||||||
);
|
await generate(
|
||||||
await generate(
|
'/',
|
||||||
'/',
|
pluginRouteContextModulePath,
|
||||||
pluginRouteContextModulePath,
|
JSON.stringify(pluginRouteContext, null, 2),
|
||||||
JSON.stringify(pluginRouteContext, null, 2),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
const actions: PluginContentLoadedActions = {
|
const actions: PluginContentLoadedActions = {
|
||||||
addRoute(initialRouteConfig) {
|
addRoute(initialRouteConfig) {
|
||||||
// Trailing slash behavior is handled generically for all plugins
|
// Trailing slash behavior is handled generically for all plugins
|
||||||
const finalRouteConfig = applyRouteTrailingSlash(
|
const finalRouteConfig = applyRouteTrailingSlash(
|
||||||
initialRouteConfig,
|
initialRouteConfig,
|
||||||
context.siteConfig,
|
context.siteConfig,
|
||||||
);
|
);
|
||||||
pluginsRouteConfigs.push({
|
pluginsRouteConfigs.push({
|
||||||
...finalRouteConfig,
|
...finalRouteConfig,
|
||||||
modules: {
|
modules: {
|
||||||
...finalRouteConfig.modules,
|
...finalRouteConfig.modules,
|
||||||
__routeContextModule: pluginRouteContextModulePath,
|
__routeContextModule: pluginRouteContextModulePath,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async createData(name, data) {
|
async createData(name, data) {
|
||||||
const modulePath = path.join(dataDir, name);
|
const modulePath = path.join(dataDir, name);
|
||||||
await generate(dataDir, name, data);
|
await generate(dataDir, name, data);
|
||||||
return modulePath;
|
return modulePath;
|
||||||
},
|
},
|
||||||
setGlobalData(data) {
|
setGlobalData(data) {
|
||||||
globalData[plugin.name] ??= {};
|
globalData[plugin.name] ??= {};
|
||||||
globalData[plugin.name]![pluginId] = data;
|
globalData[plugin.name]![pluginId] = data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const translatedContent =
|
await plugin.contentLoaded({content, actions, allContent});
|
||||||
plugin.translateContent?.({content, translationFiles}) ?? content;
|
}),
|
||||||
|
|
||||||
await plugin.contentLoaded({
|
|
||||||
content: translatedContent,
|
|
||||||
actions,
|
|
||||||
allContent,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 4. Plugin Lifecycle - routesLoaded.
|
// 4. Plugin Lifecycle - routesLoaded.
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
contentLoadedTranslatedPlugins.map(async (plugin) => {
|
loadedPlugins.map(async (plugin) => {
|
||||||
if (!plugin.routesLoaded) {
|
if (!plugin.routesLoaded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove this deprecated lifecycle soon
|
// TODO alpha-60: remove this deprecated lifecycle soon
|
||||||
// deprecated since alpha-60
|
// 1 user reported usage of this lifecycle: https://github.com/facebook/docusaurus/issues/3918
|
||||||
// TODO, 1 user reported usage of this lifecycle! https://github.com/facebook/docusaurus/issues/3918
|
|
||||||
logger.error`Plugin code=${'routesLoaded'} lifecycle is deprecated. If you think we should keep this lifecycle, please report here: url=${'https://github.com/facebook/docusaurus/issues/3918'}`;
|
logger.error`Plugin code=${'routesLoaded'} lifecycle is deprecated. If you think we should keep this lifecycle, please report here: url=${'https://github.com/facebook/docusaurus/issues/3918'}`;
|
||||||
|
|
||||||
await plugin.routesLoaded(pluginsRouteConfigs);
|
await plugin.routesLoaded(pluginsRouteConfigs);
|
||||||
|
@ -180,25 +169,5 @@ export async function loadPlugins(context: LoadContext): Promise<{
|
||||||
// routes are always placed last.
|
// routes are always placed last.
|
||||||
sortConfig(pluginsRouteConfigs, context.siteConfig.baseUrl);
|
sortConfig(pluginsRouteConfigs, context.siteConfig.baseUrl);
|
||||||
|
|
||||||
// Apply each plugin one after the other to translate the theme config
|
return {plugins: loadedPlugins, pluginsRouteConfigs, globalData};
|
||||||
const themeConfigTranslated = contentLoadedTranslatedPlugins.reduce(
|
|
||||||
(currentThemeConfig, plugin) => {
|
|
||||||
const translatedThemeConfigSlice = plugin.translateThemeConfig?.({
|
|
||||||
themeConfig: currentThemeConfig,
|
|
||||||
translationFiles: plugin.translationFiles,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...currentThemeConfig,
|
|
||||||
...translatedThemeConfigSlice,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
context.siteConfig.themeConfig,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
plugins: loadedPlugins,
|
|
||||||
pluginsRouteConfigs,
|
|
||||||
globalData,
|
|
||||||
themeConfigTranslated,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue