@ckeditor/ckeditor5-dev-manual-server 59.1.0 → 60.0.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/README.md CHANGED
@@ -8,6 +8,21 @@ Used to extend Vite to create a manual test server for CKEditor 5.
8
8
 
9
9
  More information about development tools packages can be found at the following URL: <https://github.com/ckeditor/ckeditor5-dev>.
10
10
 
11
+ ## Loading translations
12
+
13
+ Pass a language code to `manualTestsPlugin()` to load and combine that language's TypeScript translation files from the configured
14
+ package paths. The selected language becomes the default UI and content language for editors and contexts created by manual tests.
15
+ An explicit language in an editor or context configuration still takes precedence.
16
+
17
+ ```ts
18
+ manualTestsPlugin( {
19
+ paths: [ 'packages/*' ],
20
+ language: process.env.CK_LANGUAGE || 'en'
21
+ } )
22
+ ```
23
+
24
+ For example, start the manual test server in Polish using `CK_LANGUAGE=pl pnpm manual`.
25
+
11
26
  ## Changelog
12
27
 
13
28
  See the [`CHANGELOG.md`](https://github.com/ckeditor/ckeditor5-dev/blob/master/packages/ckeditor5-dev-manual-server/CHANGELOG.md) file.
package/dist/index.js CHANGED
@@ -88,6 +88,47 @@ function toManualPageEntry(relativeFilePath, workspaceRoot) {
88
88
  };
89
89
  }
90
90
  //#endregion
91
+ //#region src/manual-test-plugin/translations.ts
92
+ /**
93
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
94
+ * For licensing, see LICENSE.md.
95
+ */
96
+ const MANUAL_TRANSLATIONS_VIRTUAL_ID = "virtual:ckeditor5-manual-translations";
97
+ const RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID = `\0${MANUAL_TRANSLATIONS_VIRTUAL_ID}`;
98
+ /**
99
+ * Generates the virtual module that loads translations from every configured package root.
100
+ */
101
+ function createManualTranslationsModule(paths, language) {
102
+ const translationPatterns = paths.map((packagePath) => {
103
+ return posix.join("/", toPosixPath(packagePath), `lang/translations/${language}.ts`);
104
+ });
105
+ return `
106
+ import { add } from '@ckeditor/ckeditor5-utils';
107
+ import { Context, Editor } from '@ckeditor/ckeditor5-core';
108
+
109
+ const translationModules = import.meta.glob(
110
+ ${JSON.stringify(translationPatterns)},
111
+ { eager: true, import: 'default' }
112
+ );
113
+ const language = ${JSON.stringify(language)};
114
+ const translations = Object.values( translationModules ).map( module => module[ language ] );
115
+
116
+ if ( !translations.length ) {
117
+ throw new Error( \`No CKEditor 5 translations found for "\${ language }".\` );
118
+ }
119
+
120
+ if ( !translations.some( translation => translation.getPluralForm ) ) {
121
+ throw new Error( \`The ckeditor5-core translations for "\${ language }" were not loaded.\` );
122
+ }
123
+
124
+ for ( const translation of translations ) {
125
+ add( language, translation.dictionary, translation.getPluralForm );
126
+ }
127
+
128
+ Editor.defaultConfig = { ...Editor.defaultConfig, language: ${JSON.stringify(language)} };
129
+ Context.defaultConfig = { ...Context.defaultConfig, language: ${JSON.stringify(language)} };`;
130
+ }
131
+ //#endregion
91
132
  //#region src/manual-test-plugin/plugin.ts
92
133
  /**
93
134
  * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
@@ -137,33 +178,35 @@ function manualTestsPlugin(options) {
137
178
  slug: entry.slug
138
179
  }));
139
180
  const getManualEntriesJson = () => JSON.stringify(getClientEntries(), null, 2);
181
+ const translationsModuleCode = options.language ? createManualTranslationsModule(options.paths, options.language) : null;
140
182
  return {
141
183
  name: "ckeditor5-manual-tests",
142
- config() {
143
- return { build: { rolldownOptions: { input: getManualBuildInputs() } } };
184
+ config(config = {}) {
185
+ workspaceRoot = resolve(config.root || process.cwd());
186
+ return { input: getManualBuildInputs() };
144
187
  },
145
188
  configResolved(config) {
146
189
  workspaceRoot = config.root;
147
190
  base = config.base || "/";
148
191
  manualPagesCache.invalidate();
149
192
  packageThemeEntryCache.clear();
150
- config.build.rolldownOptions.input = getManualBuildInputs();
151
193
  },
152
194
  configureServer(server) {
153
195
  server.middlewares.use((request, _response, next) => {
154
196
  rewriteCatalogRequest(request, getManualCatalogPublicPath());
155
197
  next();
156
198
  });
157
- const clientEnvironment = server.environments.client;
158
- keepManualHtmlSourceFresh(clientEnvironment.bundledDev?.memoryFiles, getManualPages, workspaceRoot);
199
+ keepManualHtmlSourceFresh(server.environments.client.bundledDev?.memoryFiles, getManualPages, workspaceRoot);
159
200
  },
160
201
  resolveId(source) {
161
202
  if (source == MANUAL_ENTRIES_VIRTUAL_ID) return resolvedVirtualModuleId;
203
+ if (source == "virtual:ckeditor5-manual-translations" && translationsModuleCode) return RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID;
162
204
  if (isManualCatalogBuildInputSpecifier(source, getManualCatalogBuildInputFilePath(), workspaceRoot)) return getManualCatalogBuildInputFilePath();
163
205
  return null;
164
206
  },
165
207
  load(id) {
166
208
  if (id == resolvedVirtualModuleId) return `export const manualTestEntries = ${getManualEntriesJson()};`;
209
+ if (id == RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID) return translationsModuleCode;
167
210
  if (toPosixPath(id) == toPosixPath(getManualCatalogBuildInputFilePath())) return readFileSync(MANUAL_CATALOG_FILE_PATH, "utf8");
168
211
  return null;
169
212
  },
@@ -174,12 +217,13 @@ function manualTestsPlugin(options) {
174
217
  const entry = getManualPageEntryForScriptSpecifier(scriptSpecifier);
175
218
  if (!entry) return;
176
219
  const packageRootSpecifier = entry.htmlFilePath.slice(0, entry.htmlFilePath.indexOf(MANUAL_TESTS_DIRECTORY));
177
- const themeEntries = getPackageThemeEntries(packageRootSpecifier);
178
- if (!themeEntries.length) return;
220
+ const imports = getPackageThemeEntries(packageRootSpecifier).map((themeEntryFilePath) => {
221
+ return `import '${posix.relative(posix.dirname(scriptSpecifier), `${packageRootSpecifier}/${themeEntryFilePath}`)}';`;
222
+ });
223
+ if (options.language) imports.push(`import '${MANUAL_TRANSLATIONS_VIRTUAL_ID}';`);
224
+ if (!imports.length) return;
179
225
  return {
180
- code: `${code}\n${themeEntries.map((themeEntryFilePath) => {
181
- return `import '${posix.relative(posix.dirname(scriptSpecifier), `${packageRootSpecifier}/${themeEntryFilePath}`)}';`;
182
- }).join("\n")}\n`,
226
+ code: `${code}\n${imports.join("\n")}\n`,
183
227
  map: null
184
228
  };
185
229
  }
@@ -207,7 +251,7 @@ function manualTestsPlugin(options) {
207
251
  * produced by the rolldown dev engine. That engine emits the HTML output only during the initial
208
252
  * build and never regenerates it when the source `.html` changes: its bundle state reports no
209
253
  * stale output for HTML entries, `devEngine.invalidate()` throws on a non-JS module, and even a
210
- * forced full build leaves the HTML memory file untouched (verified against Vite 8.1.0). A `.html`
254
+ * forced full build leaves the HTML memory file untouched (verified against Vite 8.2.1). A `.html`
211
255
  * edit still triggers a full page reload, so without this the browser reloads into the same stale
212
256
  * HTML until the server is restarted.
213
257
  *
@@ -368,39 +412,40 @@ function getDebugFlags(debugOption = "") {
368
412
  * For licensing, see LICENSE.md.
369
413
  */
370
414
  const MANUAL_REFRESH_EVENT_NAME = "ckeditor5-manual:refresh-available";
371
- const isManualRefreshWrappedClient = Symbol("isManualRefreshWrappedClient");
415
+ const wrappedClients = /* @__PURE__ */ new WeakSet();
372
416
  function refreshPlugin() {
373
417
  return {
374
418
  name: "ckeditor5-manual-refresh",
375
419
  apply: "serve",
376
420
  configureServer(server) {
377
- const bundledDev = server.environments.client.bundledDev;
378
- if (!bundledDev) return;
379
- wrapBundledDevClientSend(bundledDev.clients);
421
+ const { bundledDev } = server.environments.client;
422
+ wrapBundledDevClientSend(bundledDev);
380
423
  wrapBundledDevFullReloads(bundledDev, server.config.root);
424
+ },
425
+ hotUpdate({ file, modules }) {
426
+ if (file.endsWith(".manual.html")) return modules;
381
427
  }
382
428
  };
383
429
  }
384
- function wrapBundledDevClientSend(clients) {
385
- if (!clients || typeof clients.setupIfNeeded != "function") return;
430
+ function wrapBundledDevClientSend(bundledDev) {
431
+ const clients = bundledDev.clients;
386
432
  const setupIfNeeded = clients.setupIfNeeded.bind(clients);
387
433
  clients.setupIfNeeded = (client, clientId) => {
388
- if (!client[isManualRefreshWrappedClient]) {
434
+ if (!wrappedClients.has(client)) {
389
435
  const send = client.send.bind(client);
390
436
  client.send = (payload) => {
391
- sendManualRefreshPayload(payload, send);
437
+ sendManualRefreshPayload(bundledDev, payload, send);
392
438
  };
393
- client[isManualRefreshWrappedClient] = true;
439
+ wrappedClients.add(client);
394
440
  }
395
441
  return setupIfNeeded(client, clientId);
396
442
  };
397
443
  }
398
444
  function wrapBundledDevFullReloads(bundledDev, workspaceRoot) {
399
- if (typeof bundledDev.handleHmrOutput != "function") return;
400
445
  const handleHmrOutput = bundledDev.handleHmrOutput.bind(bundledDev);
401
- bundledDev.handleHmrOutput = (client, files, hmrOutput, invalidateInformation) => {
402
- if (hmrOutput.type != "FullReload") return handleHmrOutput(client, files, hmrOutput, invalidateInformation);
403
- if (!shouldShowManualRefreshPromptForFiles(files)) {
446
+ bundledDev.handleHmrOutput = (client, files, hmrOutput) => {
447
+ if (hmrOutput.type != "FullReload") return handleHmrOutput(client, files, hmrOutput);
448
+ if (!shouldShowManualRefreshPrompt(files)) {
404
449
  reloadClientAfterLatestBuildOutput(bundledDev, client, files, workspaceRoot);
405
450
  return;
406
451
  }
@@ -413,25 +458,26 @@ function wrapBundledDevFullReloads(bundledDev, workspaceRoot) {
413
458
  }
414
459
  async function reloadClientAfterLatestBuildOutput(bundledDev, client, files, workspaceRoot) {
415
460
  try {
416
- await bundledDev.devEngine?.ensureLatestBuildOutput();
461
+ await bundledDev.devEngine.ensureLatestBuildOutput();
417
462
  } catch {}
463
+ const htmlFile = files.find((file) => isHtmlFile(file));
418
464
  client.send({
419
465
  type: "full-reload",
420
- path: getChangedHtmlPublicPath(files, workspaceRoot)
466
+ path: htmlFile ? toPublicFilePath(htmlFile, workspaceRoot) : void 0
421
467
  });
422
468
  }
423
- function getChangedHtmlPublicPath(files, workspaceRoot) {
424
- const htmlFile = files.find((file) => file.endsWith(".html"));
425
- return htmlFile ? toPublicFilePath(htmlFile, workspaceRoot) : void 0;
426
- }
427
469
  function ensureLatestBuildOutput(bundledDev) {
428
470
  try {
429
- bundledDev.devEngine?.ensureLatestBuildOutput().catch(() => {});
471
+ bundledDev.devEngine.ensureLatestBuildOutput().catch(() => {});
430
472
  } catch {}
431
473
  }
432
- function sendManualRefreshPayload(payload, send) {
433
- if (payload.type == "update" && !payload.updates.length) return;
434
- if (shouldShowManualRefreshPrompt(payload)) {
474
+ function sendManualRefreshPayload(bundledDev, payload, send) {
475
+ if (payload.type == "bundled-dev-update" && shouldShowManualRefreshPrompt(payload.changedIds)) {
476
+ ensureLatestBuildOutput(bundledDev);
477
+ send({
478
+ ...payload,
479
+ changedIds: []
480
+ });
435
481
  send({
436
482
  type: "custom",
437
483
  event: MANUAL_REFRESH_EVENT_NAME
@@ -440,17 +486,14 @@ function sendManualRefreshPayload(payload, send) {
440
486
  }
441
487
  send(payload);
442
488
  }
443
- function shouldShowManualRefreshPrompt(payload) {
444
- if (payload.type == "update") return !isCssUpdatePayload(payload);
445
- return false;
489
+ function shouldShowManualRefreshPrompt(filePaths) {
490
+ return filePaths.some((filePath) => !isCssFile(filePath) && !isHtmlFile(filePath));
446
491
  }
447
- function isCssUpdatePayload(payload) {
448
- return payload.type == "update" && payload.updates.length > 0 && payload.updates.every((update) => {
449
- return update.type == "css-update" || update.type == "js-update" && update.acceptedPath.endsWith(".css");
450
- });
492
+ function isCssFile(filePath) {
493
+ return filePath.split("?", 1)[0].endsWith(".css");
451
494
  }
452
- function shouldShowManualRefreshPromptForFiles(files) {
453
- return files.some((file) => !file.endsWith(".css") && !file.endsWith(".html"));
495
+ function isHtmlFile(filePath) {
496
+ return filePath.split("?", 1)[0].endsWith(".html");
454
497
  }
455
498
  //#endregion
456
499
  //#region src/svg-plugin/plugin.ts
@@ -6,5 +6,6 @@ import type { Plugin } from "vite";
6
6
  export interface ManualTestsPluginOptions {
7
7
  paths: Array<string>;
8
8
  include?: Array<string>;
9
+ language?: string;
9
10
  }
10
11
  export declare function manualTestsPlugin(options: ManualTestsPluginOptions): Plugin;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+ export declare const MANUAL_TRANSLATIONS_VIRTUAL_ID: string;
6
+ export declare const RESOLVED_MANUAL_TRANSLATIONS_VIRTUAL_ID: string;
7
+ /**
8
+ * Generates the virtual module that loads translations from every configured package root.
9
+ */
10
+ export declare function createManualTranslationsModule(paths: Array<string>, language: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckeditor/ckeditor5-dev-manual-server",
3
- "version": "59.1.0",
3
+ "version": "60.0.0",
4
4
  "description": "Used to extend Vite to create a manual test server for CKEditor 5.",
5
5
  "keywords": [],
6
6
  "author": "CKSource (http://cksource.com/)",
@@ -25,6 +25,6 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "@ckeditor/ckeditor5-inspector": "^5.0.1",
28
- "vite": "^8.1.0"
28
+ "vite": "^8.2.1"
29
29
  }
30
30
  }