deno.land / x / lume@v2.1.4 / plugins / multilanguage.ts

multilanguage.ts
نووسراو ببینە
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
import { Page } from "../core/file.ts";import { assign, merge } from "../core/utils/object.ts";import { log } from "../core/utils/log.ts";
import type Site from "../core/site.ts";import type { Data } from "../core/file.ts";
export interface Options { /** The list of extensions used for this plugin */ extensions?: string[];
/** Available languages */ languages: string[];
/** A prefix-free language */ defaultLanguage?: string;}
// Default optionsexport const defaults: Options = { extensions: [".html"], languages: [],};
export default function multilanguage(userOptions: Options) { const options = merge(defaults, userOptions);
return (site: Site) => { // Configure the merged keys options.languages.forEach((lang) => site.mergeKey(lang, "object"));
/** * Preprocessor to setup multilanguage pages * * + prevent incorrect data type of "page.data.lang" * + display guidance (warning log) to some bug-potential cases * + convert "page.data.lang" array type page (if yes) to string type page */ site.preprocess(options.extensions, (filteredPages, allPages) => { for (const page of filteredPages) { const { data } = page; const languages = data.lang as string | string[] | undefined;
// If the "lang" variable is not defined, use the default language if (languages === undefined) { data.lang = options.defaultLanguage; continue; }
// If the "lang" variable is a string, check if it's a valid language if (typeof languages === "string") { if (!options.languages.includes(languages)) { log.warn( `[multilanguage plugin] The language "${languages}" in the page ${page.sourcePath} is not defined in the "languages" option.`, ); } continue; }
// The "lang" variable of the pages must be an array if (!Array.isArray(languages)) { throw new Error(`Invalid "lang" variable in ${page.sourcePath}`); }
// Check if these "languages" are all valid language codes if (languages.some((lang) => !options.languages.includes(lang))) { log.warn( `[multilanguage plugin] One or more languages in the page ${page.sourcePath} are not defined in the "languages" option.`, ); continue; }
// Create a new page per language const newPages: Page[] = []; const id = data.id ?? page.src.path.slice(1);
for (const lang of languages) { const newData: Data = { ...data, lang, id }; const newPage = page.duplicate(undefined, newData); newPages.push(newPage); }
// Replace the current page with the multiple language versions allPages.splice(allPages.indexOf(page), 1, ...newPages); } });
/** * Preprocessor to process the multilanguage data * * + convert plain url to language url * + create the alternates * + sort the alternates */ site.preprocess(options.extensions, (pages) => { for (const page of pages) { const { data } = page; const { lang } = data;
// Resolve the language data for (const key of options.languages) { if (key in data) { if (key === lang) { assign(data, data[key]); } delete data[key]; } }
const { url } = data; const isLangUrl = url.startsWith(`/${lang}/`); const isDefaultLang = lang === options.defaultLanguage; if (!isLangUrl && !isDefaultLang) { // Preprocess to prefix all urls with the language code data.url = `/${lang}${url}`; } else if (isLangUrl && isDefaultLang) { // Preprocess to unprefix all urls with the default language code data.url = url.slice(lang.length + 1); }
// Create the alternates object if it doesn't exist const { id, type } = data; if (data.alternates || id === undefined) { data.alternates ??= [data]; continue; }
const alternates: Data[] = []; const ids = new Map<string, Page>();
pages.filter((page) => page.data.id == id && page.data.type === type) .forEach((page) => { const id = `${page.data.lang}-${page.data.id}-${page.data.type}`; const existing = ids.get(id); if (existing) { log.warn( `[multilanguage plugin] The pages ${existing.sourcePath} and ${page.sourcePath} have the same id, type and language.`, ); } ids.set(id, page); alternates.push(page.data); page.data.alternates = alternates; });
// Sort the alternates by language alternates.sort((a, b) => options.languages.indexOf(a.lang!) - options.languages.indexOf(b.lang!) ); } });
/** * Preprocessor to process the Unmatched Language URL * * + convert unmatchedLangUrl any value to URL string value */ site.preprocess(options.extensions, (pages) => { for (const page of pages) { page.data.unmatchedLangUrl = getUnmatchedLangPath( page, pages, ); } });
// Include automatically the <link rel="alternate"> elements // with the other languages site.process(options.extensions, (pages) => { for (const page of pages) { const { document } = page; const alternates = page.data.alternates; const lang = page.data.lang as string | undefined;
if (!document || !alternates || !lang) { continue; }
// Include <html lang="${lang}"> attribute element if it's missing if (!document.documentElement?.getAttribute("lang")) { document.documentElement?.setAttribute("lang", lang); }
// Insert the <link> elements automatically for (const data of alternates) { const meta = document.createElement("link"); meta.setAttribute("rel", "alternate"); meta.setAttribute("hreflang", data.lang as string); meta.setAttribute("href", site.url(data.url, true)); document.head.appendChild(meta); document.head.appendChild(document.createTextNode("\n")); }
if (page.data.unmatchedLangUrl) { appendHreflang( "x-default", site.url(page.data.unmatchedLangUrl, true), document, ); } } }); };}
function getUnmatchedLangPath( currentPage: Page<Data>, filteredPages: Page<Data>[],): string | undefined { const { sourcePath } = currentPage; const { unmatchedLangUrl, alternates } = currentPage.data;
if (!unmatchedLangUrl) return void 0;
// If unmatchedLang is an external URL string if (URL.canParse(unmatchedLangUrl)) { return unmatchedLangUrl; }
// If unmatchedLang is an source path string if (unmatchedLangUrl.startsWith("/")) { const langSelectorPage = filteredPages.some( (page) => page.data.url === unmatchedLangUrl, );
if (!langSelectorPage) { log.warn( `[multilanguage plugin] The URL <cyan>${unmatchedLangUrl}</cyan> of unmatchedLangUrl option is not found in ${sourcePath}.`, ); } return langSelectorPage ? unmatchedLangUrl : void 0; }
// If unmatchedLang is language code → resolve to URL of that language const lang = alternates?.find((data) => data.lang === unmatchedLangUrl); if (!lang) { log.warn( `[multilanguage plugin] The URL for lang code "${unmatchedLangUrl}" of unmatchedLangUrl option is not found in ${sourcePath}.`, ); } return lang?.url;}
function appendHreflang(lang: string, url: string, document: Document) { const meta = document.createElement("link"); meta.setAttribute("rel", "alternate"); meta.setAttribute("hreflang", lang); meta.setAttribute("href", url); document.head.appendChild(meta); document.head.appendChild(document.createTextNode("\n"));}
lume

Version Info

Tagged at
5 months ago