docpensieve 0.4.0 → 0.5.0-beta.2

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.
@@ -34,6 +34,7 @@ program
34
34
  .option('-t, --theme <framework>', 'tailwind | custom')
35
35
  .option('-u, --site-url <url>', 'public URL of the site')
36
36
  .option('--version-name <version>', 'first version, e.g. 1.0')
37
+ .option('--translation <code>', 'code of a second language, e.g. fr')
37
38
  .option('-y, --yes', 'accept the defaults without a dialogue')
38
39
  .option('-f, --force', 'overwrite an existing configuration')
39
40
  .option('--minimal', "leave DocPensieve's documentation out of the new site")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "docpensieve",
3
- "version": "0.4.0",
3
+ "version": "0.5.0-beta.2",
4
4
  "description": "DocPensieve command-line interface (init, build, check, dev, serve)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,10 +24,10 @@
24
24
  "types"
25
25
  ],
26
26
  "dependencies": {
27
- "@docpensieve/components": "0.4.0",
28
- "@docpensieve/core": "0.4.0",
29
- "@docpensieve/shared": "0.4.0",
30
- "@docpensieve/theme": "0.4.0",
27
+ "@docpensieve/components": "0.5.0-beta.2",
28
+ "@docpensieve/core": "0.5.0-beta.2",
29
+ "@docpensieve/shared": "0.5.0-beta.2",
30
+ "@docpensieve/theme": "0.5.0-beta.2",
31
31
  "chalk": "^6.0.0",
32
32
  "chokidar": "^5.0.0",
33
33
  "commander": "^15.0.0"
@@ -18,6 +18,8 @@ import {
18
18
  DocPensieveError,
19
19
  THEME_FOLDER,
20
20
  THEME_FRAMEWORKS,
21
+ isLanguageCode,
22
+ languageName,
21
23
  } from '@docpensieve/shared';
22
24
 
23
25
  /**
@@ -30,7 +32,16 @@ const FRAMEWORK_LABELS = {
30
32
  };
31
33
 
32
34
  /** Answers used when there is no dialogue. */
33
- const DEFAULTS = { name: 'My documentation', siteUrl: '', theme: 'tailwind', version: '1.0' };
35
+ const DEFAULTS = {
36
+ name: 'My documentation',
37
+ siteUrl: '',
38
+ theme: 'tailwind',
39
+ version: '1.0',
40
+ translation: '',
41
+ };
42
+
43
+ /** Second language offered first, being the one whose wording ships too. */
44
+ const DEFAULT_TRANSLATION = 'fr';
34
45
 
35
46
  /**
36
47
  * Folder of the installed DocPensieve documentation, inside the version folder.
@@ -125,15 +136,43 @@ async function askDocumentation(rl) {
125
136
  }
126
137
  }
127
138
 
139
+ /**
140
+ * Asks whether the site will carry a second language, and which one.
141
+ *
142
+ * Asked rather than left to the configuration: a translation changes the
143
+ * shape of the project — a folder per language beside the pages — and that is
144
+ * cheaper to set up at the start than to retrofit.
145
+ *
146
+ * @param {import('node:readline/promises').Interface} rl
147
+ * @returns {Promise<string>} Language code, or `''` for a single language.
148
+ */
149
+ async function askTranslation(rl) {
150
+ for (;;) {
151
+ const answer = (await rl.question('Will the site be in several languages? [y/N]: '))
152
+ .trim()
153
+ .toLowerCase();
154
+ if (answer === '' || answer === 'n' || answer === 'no') return '';
155
+ if (answer === 'y' || answer === 'yes') break;
156
+ console.log('Answer not understood. Expected: y or n.');
157
+ }
158
+
159
+ for (;;) {
160
+ const code = (await ask(rl, 'Code of the second language', DEFAULT_TRANSLATION)).trim();
161
+ if (isLanguageCode(code)) return code;
162
+ console.log(`"${code}" does not name a language. Expected a code: fr, de, pt-BR, zh-Hans.`);
163
+ }
164
+ }
165
+
128
166
  /**
129
167
  * Gathers the answers, through a dialogue or from the options.
130
168
  *
131
169
  * @param {{
132
170
  * name?: string, theme?: string, siteUrl?: string, version?: string,
133
- * yes?: boolean, minimal?: boolean,
171
+ * translation?: string, yes?: boolean, minimal?: boolean,
134
172
  * }} options
135
173
  * @returns {Promise<{
136
174
  * name: string, theme: string, siteUrl: string, version: string, docs: boolean,
175
+ * translation: string,
137
176
  * }>}
138
177
  */
139
178
  async function collect(options) {
@@ -142,6 +181,7 @@ async function collect(options) {
142
181
  siteUrl: options.siteUrl ?? DEFAULTS.siteUrl,
143
182
  theme: options.theme ?? DEFAULTS.theme,
144
183
  version: options.version ?? DEFAULTS.version,
184
+ translation: options.translation ?? DEFAULTS.translation,
145
185
  docs: !options.minimal,
146
186
  };
147
187
 
@@ -154,7 +194,7 @@ async function collect(options) {
154
194
  if (!process.stdin.isTTY) {
155
195
  console.log('No interactive terminal: no questions asked, the options and defaults apply.');
156
196
  console.log(
157
- 'To choose, pass --name, --site-url, --theme, --version-name or --minimal; --yes silences this notice.',
197
+ 'To choose, pass --name, --site-url, --theme, --version-name, --translation or --minimal; --yes silences this notice.',
158
198
  );
159
199
  return fromOptions;
160
200
  }
@@ -164,9 +204,10 @@ async function collect(options) {
164
204
  const name = await ask(rl, 'Project name', fromOptions.name);
165
205
  const siteUrl = await ask(rl, 'Public URL of the site (optional)', fromOptions.siteUrl);
166
206
  const version = await ask(rl, 'First version', fromOptions.version);
207
+ const translation = options.translation ?? (await askTranslation(rl));
167
208
  const theme = options.theme ?? (await askFramework(rl));
168
209
  const docs = options.minimal ? false : await askDocumentation(rl);
169
- return { name, siteUrl, version, theme, docs };
210
+ return { name, siteUrl, version, theme, docs, translation };
170
211
  } finally {
171
212
  rl.close();
172
213
  }
@@ -183,6 +224,18 @@ async function collect(options) {
183
224
  */
184
225
  const quote = (value) => `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
185
226
 
227
+ /**
228
+ * Writes a language code as a property name.
229
+ *
230
+ * `fr` stands on its own; `pt-BR` and `zh-Hans` carry a hyphen, which is a
231
+ * minus sign to JavaScript — unquoted, the generated configuration would not
232
+ * parse.
233
+ *
234
+ * @param {string} code
235
+ * @returns {string}
236
+ */
237
+ const key = (code) => (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(code) ? code : quote(code));
238
+
186
239
  /**
187
240
  * Renders `docpensieve.config.mjs`.
188
241
  *
@@ -190,10 +243,13 @@ const quote = (value) => `'${String(value).replace(/\\/g, '\\\\').replace(/'/g,
190
243
  * or commented out with an example — so that the first file a user opens also
191
244
  * tells them everything they can change.
192
245
  *
193
- * @param {{ name: string, theme: string, siteUrl: string, version: string }} answers
246
+ * @param {{
247
+ * name: string, theme: string, siteUrl: string, version: string,
248
+ * translation?: string,
249
+ * }} answers
194
250
  * @returns {string} Contents of `docpensieve.config.mjs`.
195
251
  */
196
- function renderConfig({ name, theme, siteUrl, version }) {
252
+ function renderConfig({ name, theme, siteUrl, version, translation = '' }) {
197
253
  const slug = versionSlug(version);
198
254
  // A type annotation rather than an `import`: `defineConfig` transforms
199
255
  // nothing, it is only there for autocompletion. Actually importing it would
@@ -233,9 +289,14 @@ function renderConfig({ name, theme, siteUrl, version }) {
233
289
  ` name: ${quote(version)}, // label in the version switcher`,
234
290
  ` folder: ${quote(`docs/${slug}`)},`,
235
291
  ' current: true,',
236
- ' // Pages of this version in another language, served under /fr/.',
292
+ ` // Pages of this version in another language, served under /${translation || 'fr'}/.`,
237
293
  ' // Your own language stays where it is, and keeps its addresses.',
238
- ` // translations: { fr: ${quote(`docs/${slug}-fr`)} },`,
294
+ // Written out, not left as an example, once the language is known: the
295
+ // field is the whole of the feature, and the folder beside it already
296
+ // holds a page.
297
+ translation
298
+ ? ` translations: { ${key(translation)}: ${quote(`docs/${slug}-${translation}`)} },`
299
+ : ` // translations: { fr: ${quote(`docs/${slug}-fr`)} },`,
239
300
  ' },',
240
301
  ' ],',
241
302
  '',
@@ -353,11 +414,12 @@ function versionSlug(version) {
353
414
  }
354
415
 
355
416
  /**
356
- * @param {string} name Project name, for the title of the home page.
357
- * @param {boolean} docs Whether DocPensieve's documentation is installed.
417
+ * Home page of the project, in the language of the site.
418
+ *
419
+ * @param {{ name: string, docs: boolean, slug: string, translation: string }} answers
358
420
  * @returns {string}
359
421
  */
360
- const renderIndex = (name, docs) => `---
422
+ const renderIndex = ({ name, docs, slug, translation }) => `---
361
423
  title: Introduction
362
424
  description: Documentation of ${name}.
363
425
  date: ${new Date().toISOString().slice(0, 10)}
@@ -378,6 +440,20 @@ without appearing in the URL.
378
440
 
379
441
  See the [installation guide](/guide/installation/).
380
442
  ${
443
+ translation
444
+ ? `
445
+ ## In ${languageName(translation)}
446
+
447
+ The same pages live in \`docs/${slug}-${translation}/\`, and the language switcher
448
+ in the header moves between them.
449
+
450
+ A page with no twin there does not exist in that language: it stays out of the
451
+ menu and out of the sitemap, and the switcher names the language without
452
+ offering it. The installation page is in that case — write
453
+ \`docs/${slug}-${translation}/01-guide/01-installation.md\` and it appears.
454
+ ${docs ? '\nThe [languages guide](/docpensieve/guide/languages/) covers the rest.\n' : ''}`
455
+ : ''
456
+ }${
381
457
  docs
382
458
  ? `
383
459
  ## Learning DocPensieve
@@ -389,6 +465,70 @@ tool that builds this site, installed along with it. Delete its folder,
389
465
  : ''
390
466
  }`;
391
467
 
468
+ /**
469
+ * Home page of the second language.
470
+ *
471
+ * French is written out, the tool shipping its wording too. Any other
472
+ * language gets the page in English, saying in its first line that it is
473
+ * there to be translated: a copy passing for a translation is the one failure
474
+ * this feature invites — the reader gets English under an address that
475
+ * promised their language, and nothing reports it.
476
+ *
477
+ * Only the home page is written. Its twin, the installation page, is left
478
+ * untranslated on purpose: it is what shows that an untranslated page does
479
+ * not exist in that language, which no sentence explains as well as the menu
480
+ * that lacks it.
481
+ *
482
+ * @param {{ name: string, slug: string, translation: string }} answers
483
+ * @returns {string}
484
+ */
485
+ function renderTranslatedIndex({ name, slug, translation }) {
486
+ const french = translation.toLowerCase().split('-')[0] === 'fr';
487
+ const head = `---
488
+ title: Introduction
489
+ description: ${french ? `Documentation de ${name}.` : `Documentation of ${name}.`}
490
+ date: ${new Date().toISOString().slice(0, 10)}
491
+
492
+ jsonld:
493
+ type: TechArticle
494
+ breadcrumbs: true
495
+ ---
496
+
497
+ # ${name}
498
+ `;
499
+
500
+ if (french) {
501
+ return `${head}
502
+ Bienvenue dans la documentation.
503
+
504
+ ## Une page et sa jumelle
505
+
506
+ Cette page est la version française de \`docs/${slug}/index.md\`. Le sélecteur
507
+ de langue, dans l'en-tête, passe de l'une à l'autre.
508
+
509
+ La page d'installation, elle, n'est pas traduite : elle **n'existe pas** en
510
+ français. Elle ne figure ni dans le menu ni dans le plan du site, et le
511
+ sélecteur la nomme sans la proposer. Écrivez
512
+ \`docs/${slug}-${translation}/01-guide/01-installation.md\` pour la voir apparaître.
513
+ `;
514
+ }
515
+
516
+ return `${head}
517
+ **Replace this page with your translation.** It is the ${languageName(translation)}
518
+ twin of \`docs/${slug}/index.md\`, written in English so that the site builds:
519
+ left as it is, a reader who picks ${languageName(translation, translation)} gets English.
520
+
521
+ ## One page, two languages
522
+
523
+ The language switcher in the header moves between this page and its twin.
524
+
525
+ The installation page has no twin here, so it does not exist in this language:
526
+ it stays out of the menu and out of the sitemap, and the switcher names the
527
+ language without offering it. Write
528
+ \`docs/${slug}-${translation}/01-guide/01-installation.md\` and it appears.
529
+ `;
530
+ }
531
+
392
532
  /** @returns {string} Sample page, showing ordering and highlighting. */
393
533
  const renderGuide = () => `---
394
534
  title: Installation
@@ -478,11 +618,14 @@ async function installDocumentation(source, target, slug) {
478
618
  * @param {string} [dir] Target folder, created if needed.
479
619
  * @param {{
480
620
  * name?: string, theme?: string, siteUrl?: string, version?: string,
481
- * yes?: boolean, force?: boolean, minimal?: boolean,
482
- * }} [options] `minimal` leaves DocPensieve's documentation out of the site.
483
- * @returns {Promise<{ dir: string, theme: string, docs: boolean }>}
484
- * @throws {DocPensieveError} Unknown framework, project already initialised,
485
- * or documentation to install missing.
621
+ * translation?: string, yes?: boolean, force?: boolean, minimal?: boolean,
622
+ * }} [options] `minimal` leaves DocPensieve's documentation out of the site;
623
+ * `translation` is the code of a second language, `fr` for instance.
624
+ * @returns {Promise<{
625
+ * dir: string, theme: string, docs: boolean, translation: string,
626
+ * }>}
627
+ * @throws {DocPensieveError} Unknown framework, unknown language, project
628
+ * already initialised, or documentation to install missing.
486
629
  */
487
630
  export async function init(dir = '.', options = {}) {
488
631
  const target = path.resolve(dir);
@@ -504,6 +647,14 @@ export async function init(dir = '.', options = {}) {
504
647
  });
505
648
  }
506
649
 
650
+ // Checked here for the same reason as the framework: a code refused after
651
+ // five questions would be five questions wasted.
652
+ if (options.translation && !isLanguageCode(options.translation)) {
653
+ throw new DocPensieveError(`"${options.translation}" does not name a language.`, {
654
+ hint: 'Write the code, not the name: "fr" for French, "pt-BR", "zh-Hans". It becomes the lang of the document and a segment of the address.',
655
+ });
656
+ }
657
+
507
658
  const answers = await collect(options);
508
659
 
509
660
  // Located before anything is written: a project left half set up would be
@@ -535,8 +686,21 @@ export async function init(dir = '.', options = {}) {
535
686
 
536
687
  await mkdir(path.join(docsDir, '01-guide'), { recursive: true });
537
688
  await writeFile(configPath, renderConfig(answers), 'utf8');
538
- await writeFile(path.join(docsDir, 'index.md'), renderIndex(answers.name, answers.docs), 'utf8');
689
+ await writeFile(
690
+ path.join(docsDir, 'index.md'),
691
+ renderIndex({ name: answers.name, docs: answers.docs, slug, translation: answers.translation }),
692
+ 'utf8',
693
+ );
539
694
  await writeFile(path.join(docsDir, '01-guide', '01-installation.md'), renderGuide(), 'utf8');
695
+ if (answers.translation) {
696
+ const folder = path.join(target, 'docs', `${slug}-${answers.translation}`);
697
+ await mkdir(folder, { recursive: true });
698
+ await writeFile(
699
+ path.join(folder, 'index.md'),
700
+ renderTranslatedIndex({ name: answers.name, slug, translation: answers.translation }),
701
+ 'utf8',
702
+ );
703
+ }
540
704
  if (documentation) {
541
705
  await installDocumentation(documentation, path.join(docsDir, DOCS_FOLDER), slug);
542
706
  }
@@ -548,14 +712,31 @@ export async function init(dir = '.', options = {}) {
548
712
  console.log(` ${CONFIG_FILENAME}`);
549
713
  console.log(` docs/${slug}/index.md`);
550
714
  console.log(` docs/${slug}/01-guide/01-installation.md`);
715
+ if (answers.translation) {
716
+ const state =
717
+ answers.translation.toLowerCase().split('-')[0] === 'fr'
718
+ ? `in ${languageName(answers.translation)}`
719
+ : 'to translate';
720
+ console.log(` docs/${slug}-${answers.translation}/index.md the home page, ${state}`);
721
+ }
551
722
  if (documentation) {
552
723
  console.log(` docs/${slug}/${DOCS_FOLDER}/ DocPensieve's documentation, to delete when done`);
553
724
  }
554
725
  for (const line of stylesheets) console.log(` ${line}`);
555
726
  console.log(`\nTheme: ${answers.theme} — ${FRAMEWORK_LABELS[answers.theme]}`);
727
+ if (answers.translation) {
728
+ console.log(
729
+ `Second language: ${languageName(answers.translation)} (${answers.translation}) — served under /${answers.translation}/`,
730
+ );
731
+ }
556
732
  console.log('\nNext: npx docpensieve dev');
557
733
 
558
- return { dir: target, theme: answers.theme, docs: answers.docs };
734
+ return {
735
+ dir: target,
736
+ theme: answers.theme,
737
+ docs: answers.docs,
738
+ translation: answers.translation,
739
+ };
559
740
  }
560
741
 
561
742
  /**
@@ -46,6 +46,7 @@ npx docpensieve init my-site --yes --name "My documentation"
46
46
  | `-t, --theme <framework>` | `tailwind` or `custom` |
47
47
  | `-u, --site-url <url>` | Public URL, from which the deployment prefix is derived |
48
48
  | `--version-name <version>` | First version, `1.0` for instance |
49
+ | `--translation <code>` | Code of a second language, `fr` for instance |
49
50
  | `-y, --yes` | Accepts the defaults without a dialogue |
50
51
  | `-f, --force` | Overwrites an existing configuration |
51
52
  | `--minimal` | Leaves DocPensieve's documentation out of the site |
@@ -99,8 +100,9 @@ The documentation `init` installed in `99-docpensieve` stays at the version it
99
100
  came with. To refresh it, run `init` in a scratch folder and copy that folder
100
101
  over.
101
102
 
102
- Coming from the 0.3, [Migrate from 0.3 to 0.4](./migrate-from-0-3/) says what
103
- changes on its own and what to check first.
103
+ Coming from the 0.4, [Migrate from latest to beta](./migrate-to-beta/) says
104
+ what changes on its own and what to check first. To try this beta, install
105
+ `docpensieve@beta`, or run `npx docpensieve@beta` alone.
104
106
 
105
107
  ## Checking
106
108
 
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: Migrate from latest to beta
3
+ description: Move a project from the latest version, the 0.4, to the 0.5 beta — what changes on its own, and what to check.
4
+ tags: [guide, migration]
5
+
6
+ jsonld:
7
+ type: TechArticle
8
+ breadcrumbs: true
9
+ ---
10
+
11
+ # Migrate from latest to beta
12
+
13
+ A project on the latest version — the 0.4 — builds with the 0.5 beta as it is:
14
+ every field the 0.5 adds is optional.
15
+
16
+ ## Update
17
+
18
+ ```bash
19
+ npm install docpensieve@beta
20
+ ```
21
+
22
+ Through `npx` alone, `npx docpensieve@beta` runs the beta. Going back is
23
+ `npm install docpensieve@latest`.
24
+
25
+ Read your site back once after the update — it is the cheapest check there is:
26
+
27
+ ```bash
28
+ npx docpensieve build
29
+ npx docpensieve check
30
+ ```
31
+
32
+ ## What changes on its own
33
+
34
+ Nothing yet: the 0.5 has just opened. Each change is listed here as it lands,
35
+ beside what it asks of a project already built on the 0.4.
36
+
37
+ ## What to check afterwards
38
+
39
+ - **Your own CSS**, if the theme folder styles anything the beta touches.
40
+ - **`npx docpensieve check`**, which reads the built site back and reports dead
41
+ links and invalid markup.
@@ -13,6 +13,19 @@ jsonld:
13
13
  A version can be published in several languages. Each one is a folder of pages
14
14
  of its own, standing beside the version it translates.
15
15
 
16
+ ## Starting from init
17
+
18
+ `init` asks whether the site will be in several languages. Answer yes, give a
19
+ code, and the project comes out wired: the folder beside your pages, the field
20
+ in the configuration, and a home page in that language to start from.
21
+
22
+ ```bash
23
+ npx docpensieve init my-site --translation fr
24
+ ```
25
+
26
+ Everything below is what that answer sets up — and what to do when you add a
27
+ language to a project that already exists.
28
+
16
29
  ## Declaring a translation
17
30
 
18
31
  ```js
@@ -29,6 +29,7 @@ npx docpensieve init [dir]
29
29
  | `-t, --theme <framework>` | `tailwind` or `custom` |
30
30
  | `-u, --site-url <url>` | Public URL of the site |
31
31
  | `--version-name <version>` | First version, `1.0` for instance |
32
+ | `--translation <code>` | Code of a second language, `fr` |
32
33
  | `-y, --yes` | Accepts the defaults without a dialogue |
33
34
  | `-f, --force` | Overwrites an existing configuration |
34
35
  | `--minimal` | Leaves DocPensieve's documentation out |
@@ -346,6 +346,39 @@ a language nobody thought of.
346
346
 
347
347
  **Returns** `'ltr' \| 'rtl'` — `ltr` when the language is unknown — the safe default, and what every page did before this existed.
348
348
 
349
+ ### `isLanguageCode`
350
+
351
+ `isLanguageCode(value)`
352
+
353
+ Whether a string names a language, as BCP 47 and CLDR understand it.
354
+
355
+ The standard is BCP 47, and `Intl` carries it: a regex of our own refused
356
+ `zh-Hans-CN`, which is valid, and accepted shapes that are not. Well formed
357
+ is not the same as real, though — BCP 47 allows a language subtag of five
358
+ to eight letters, so `francais` passes that check and would land in the
359
+ markup as `lang="francais"`, a value no browser maps to a language. CLDR
360
+ knows which tags name one; the subtag alone is asked, so that a region, a
361
+ script or a private extension does not get in the way.
362
+
363
+ | Parameter | Type | |
364
+ | --- | --- | --- |
365
+ | `value` | `string` | |
366
+
367
+ **Returns** `boolean`
368
+
369
+ ### `languageName`
370
+
371
+ `languageName(code, [inLang])`
372
+
373
+ Name of a language, written in a language.
374
+
375
+ | Parameter | Type | |
376
+ | --- | --- | --- |
377
+ | `code` | `string` | Language code. |
378
+ | `[inLang]` | `string` | Language the name is written in. |
379
+
380
+ **Returns** `string` — The name, or the code itself when nothing names it.
381
+
349
382
  ## `@docpensieve/core`
350
383
 
351
384
  Configuration, loading, compilation, structured data and generation.
@@ -1211,11 +1244,11 @@ Sets up a documentation project.
1211
1244
  | Parameter | Type | |
1212
1245
  | --- | --- | --- |
1213
1246
  | `[dir]` | `string` | Target folder, created if needed. |
1214
- | `[options]` | `{ name?: string, theme?: string, siteUrl?: string, version?: string, yes?: boolean, force?: boolean, minimal?: boolean, }` | `minimal` leaves DocPensieve's documentation out of the site. |
1247
+ | `[options]` | `{ name?: string, theme?: string, siteUrl?: string, version?: string, translation?: string, yes?: boolean, force?: boolean, minimal?: boolean, }` | `minimal` leaves DocPensieve's documentation out of the site; `translation` is the code of a second language, `fr` for instance. |
1215
1248
 
1216
- **Returns** `Promise<{ dir: string, theme: string, docs: boolean }>`
1249
+ **Returns** `Promise<{ dir: string, theme: string, docs: boolean, translation: string, }>`
1217
1250
 
1218
- **Throws** `DocPensieveError` — Unknown framework, project already initialised, or documentation to install missing.
1251
+ **Throws** `DocPensieveError` — Unknown framework, unknown language, project already initialised, or documentation to install missing.
1219
1252
 
1220
1253
  ### `serve`
1221
1254
 
@@ -1,92 +1,46 @@
1
1
  ---
2
- title: What's new in 0.4
3
- description: What the 0.4 brings, and what it changes for a 0.3 project.
2
+ title: What's new in 0.5
3
+ description: What the 0.5 brings, and what it changes for a 0.4 project.
4
4
  tags: [release]
5
5
  ---
6
6
 
7
- # What's new in 0.4
7
+ # What's new in 0.5
8
8
 
9
- The 0.4 is out, and installs by default:
9
+ This version is **in preparation**. Its betas go out under the npm tag `beta`,
10
+ while the version installed by default stays the 0.4, documented in the
11
+ `latest` pages of this site:
10
12
 
11
13
  ```bash
12
- npx docpensieve init my-site
14
+ npx docpensieve@beta init my-site
13
15
  ```
14
16
 
15
- Each feature below links to the guide and to the reference: this page gathers
16
- them, it is never the only place something is written.
17
+ Each feature is announced here as it lands, with a link to the guide and to the
18
+ reference: this page announces, it is never the only place something is
19
+ written.
17
20
 
18
- ## What it brings
21
+ ## Already there
19
22
 
20
- The 0.4 turns on the navigation. A site of forty pages was asking its reader to
21
- scroll past what did not concern them; it can now fold its menu, carry its own
22
- links in the header, and set a passage apart in the middle of a page.
23
+ ### init asks about languages
23
24
 
24
- ### A menu that folds
25
+ `init` now asks whether the site will be in several languages, and sets the
26
+ answer up: the folder beside your pages, `translations` written out in the
27
+ configuration, and a home page in that language to start from. The second
28
+ sample page is deliberately left untranslated, so that the menu shows what an
29
+ untranslated page does — nothing, in that language.
25
30
 
26
- `foldedSidebar: true` folds the categories of the documentation menu, and opens
27
- the branch the reader stands in. On a narrow screen the whole menu folds above
28
- the content, whatever that setting — nothing to configure for that one. See
29
- [Navigation](./guide/navigation/).
30
-
31
- ### Links and panels in the header
32
-
33
- A `headerLinks` entry adds a link beside the version switcher. An entry
34
- carrying `columns` opens a **panel** of links instead of leading anywhere, so a
35
- site can navigate from the header alone, from the menu alone, or from both with
36
- different links. A `version` field points every version at a single one, which
37
- is how this site leads to its examples. See
38
- [Navigation](./guide/navigation/) and
39
- [the configuration reference](./reference/configuration/).
40
-
41
- ### A header held, or not
42
-
43
- `stickyHeader` decides whether the header holds to the top of the screen or
44
- scrolls away with the page, giving its height back to the text. It stays held
45
- by default. See [Navigation](./guide/navigation/).
46
-
47
- ### A menu of links, anywhere in a page
48
-
49
- The `Menu` component places a row of links where a page needs them — a summary
50
- at the top of a landing page, the chapters of a guide. Entries can be grouped
51
- under a title, and the row folds behind a button on a narrow screen, without a
52
- script. See [Menu](./components/menu/).
53
-
54
- ### Blocks that stand apart
55
-
56
- `Admonition` sets a passage apart and says how to read it: `note`, `info`,
57
- `tip`, `attention`, `alert`, `danger`. A project declares **its own kinds** in
58
- the `admonitions` field — a label and a tone taken from the theme — so the list
59
- does not have to grow for a team to have the block it needs. See
60
- [Admonition](./components/admonition/).
61
-
62
- ### Icons from a set
63
-
64
- `LogoIcon` accepts the name of an icon from a collection, written
65
- `simple-icons:github`, beside a file of your project. The set is a package your
66
- project installs and the drawing is placed in the page at the build, like any
67
- other icon: your reader downloads nothing, and no request leaves their browser.
68
- An admonition kind takes its mark the same way. See
69
- [LogoIcon](./components/logo-icon/).
70
-
71
- ### A site in several languages
72
-
73
- A version declares the folder of each translation:
74
-
75
- ```js
76
- versions: [
77
- { slug: 'latest', name: '1.0', folder: 'docs/v1.0', current: true,
78
- translations: { fr: 'docs/v1.0-fr' } },
79
- ],
31
+ ```bash
32
+ npx docpensieve init my-site --translation fr
80
33
  ```
81
34
 
82
- The language of the site keeps the addresses it has, and a translation is
83
- served under its code `/versions/latest/fr/`. The shell follows: English and
84
- French ship with the tool, and the `ui` field corrects a word or adds a
85
- language. A page nobody translated is not offered in that language rather than
86
- served in another. See [Languages](./guide/languages/).
35
+ A code is a BCP 47 one: `fr`, `pt-BR`, `zh-Hans`. French and English ship with
36
+ their wording for the shell; any other language keeps the English wording
37
+ until the `ui` field gives it its own.
38
+
39
+ [Languages](./guide/languages/) · [The init options](./reference/cli/)
87
40
 
88
- ## For a 0.3 project
41
+ ## For a 0.4 project
89
42
 
90
- Nothing to change: a 0.3 configuration builds as it is, every new field being
91
- optional. [Migrate from 0.3 to 0.4](./guide/migrate-from-0-3/) lists what
92
- changes on its own, and what is worth turning on.
43
+ Nothing to change: a 0.4 configuration builds as it is, and a site already in
44
+ two languages is untouched the question only shapes a **new** project.
45
+ [Migrate from latest to beta](./guide/migrate-to-beta/) lists what changes on
46
+ its own, and what is worth turning on.
@@ -9,17 +9,21 @@
9
9
  * @param {string} [dir] Target folder, created if needed.
10
10
  * @param {{
11
11
  * name?: string, theme?: string, siteUrl?: string, version?: string,
12
- * yes?: boolean, force?: boolean, minimal?: boolean,
13
- * }} [options] `minimal` leaves DocPensieve's documentation out of the site.
14
- * @returns {Promise<{ dir: string, theme: string, docs: boolean }>}
15
- * @throws {DocPensieveError} Unknown framework, project already initialised,
16
- * or documentation to install missing.
12
+ * translation?: string, yes?: boolean, force?: boolean, minimal?: boolean,
13
+ * }} [options] `minimal` leaves DocPensieve's documentation out of the site;
14
+ * `translation` is the code of a second language, `fr` for instance.
15
+ * @returns {Promise<{
16
+ * dir: string, theme: string, docs: boolean, translation: string,
17
+ * }>}
18
+ * @throws {DocPensieveError} Unknown framework, unknown language, project
19
+ * already initialised, or documentation to install missing.
17
20
  */
18
21
  export declare function init(dir?: string, options?: {
19
22
  name?: string;
20
23
  theme?: string;
21
24
  siteUrl?: string;
22
25
  version?: string;
26
+ translation?: string;
23
27
  yes?: boolean;
24
28
  force?: boolean;
25
29
  minimal?: boolean;
@@ -27,4 +31,5 @@ export declare function init(dir?: string, options?: {
27
31
  dir: string;
28
32
  theme: string;
29
33
  docs: boolean;
34
+ translation: string;
30
35
  }>;
@@ -1,125 +0,0 @@
1
- ---
2
- title: Migrate from 0.3 to 0.4
3
- description: Move a project from the 0.3 to the 0.4 — what changes on its own, and what to turn on.
4
- tags: [guide, migration]
5
- ---
6
-
7
- # Migrate from 0.3 to 0.4
8
-
9
- A project on the 0.3 builds with the 0.4 as it is: every field the 0.4 adds is
10
- optional, and leaving them out keeps the site you have.
11
-
12
- ## Update
13
-
14
- ```bash
15
- npm install docpensieve@latest
16
- ```
17
-
18
- Through `npx` alone, `npx docpensieve` already runs it.
19
-
20
- Read your site back once after the update — it is the cheapest check there is:
21
-
22
- ```bash
23
- npx docpensieve build
24
- npx docpensieve check
25
- ```
26
-
27
- ## What changes on its own
28
-
29
- **On a narrow screen, the documentation menu now folds above the content**
30
- instead of standing open between the header and the text. Nothing to
31
- configure, and nothing to undo: on a wide screen the menu is unchanged.
32
-
33
- Nothing else moves. Your pages, your configuration and your theme folder are
34
- read exactly as before.
35
-
36
- ## What is worth turning on
37
-
38
- Each of these is one line of `docpensieve.config.mjs`. None depends on
39
- another.
40
-
41
- ### A menu that folds
42
-
43
- Past twenty or so pages, a menu that shows everything asks the reader to scroll
44
- past what does not concern them:
45
-
46
- ```js
47
- foldedSidebar: true,
48
- ```
49
-
50
- The categories fold, and the branch of the page being read opens on its own.
51
- A category that is also a page keeps its page as the first entry — the handle
52
- of a fold cannot be a link.
53
-
54
- ### Links in the header
55
-
56
- ```js
57
- headerLinks: [
58
- { label: 'Blog', href: '/blog/' },
59
- { label: 'Repository', href: 'https://example.com/repo' },
60
- ],
61
- ```
62
-
63
- A target starts from the root of a version, or is a full address. An entry
64
- carrying `columns` opens a panel instead of leading anywhere; `href` and
65
- `columns` together are refused, an entry doing one thing or the other.
66
-
67
- See [Navigation](./navigation/) for the panel and for what happens on a phone.
68
-
69
- ### A header that scrolls away
70
-
71
- ```js
72
- stickyHeader: false,
73
- ```
74
-
75
- The header gives its height back to the text instead of holding to the top of
76
- the screen. It stays held by default.
77
-
78
- ### Blocks that stand apart
79
-
80
- Six kinds ship — `note`, `info`, `tip`, `attention`, `alert`, `danger` — and
81
- need no configuration:
82
-
83
- ```mdx
84
- <Admonition type="attention">Read this before upgrading.</Admonition>
85
- ```
86
-
87
- Kinds of your own take a label and a tone:
88
-
89
- ```js
90
- admonitions: {
91
- review: { label: 'To review', tone: 'attention' },
92
- },
93
- ```
94
-
95
- A kind nobody declared stops the build rather than rendering a block with no
96
- colour and no label. See [Admonition](../components/admonition/).
97
-
98
- ### Icons from a set
99
-
100
- Beside a file of your project, `LogoIcon` accepts the name of an icon from a
101
- collection:
102
-
103
- ```bash
104
- npm install --save-dev @iconify-json/simple-icons
105
- ```
106
-
107
- ```mdx
108
- <LogoIcon src="simple-icons:github" label="Repository" />
109
- ```
110
-
111
- The set is read at the build and the drawing placed in the page: no request
112
- leaves your reader's browser. See [LogoIcon](../components/logo-icon/).
113
-
114
- ## What to check afterwards
115
-
116
- - **Your own CSS**, if the theme folder styles the header or the menu: the
117
- folded menu adds `details` and `summary` elements where there were only
118
- links, and a static header no longer carries `position: sticky`.
119
- - **A link to a heading**, if you turned the sticky header off: the space kept
120
- above an anchor goes away with it, which is the point.
121
- - **`npx docpensieve check`**, which reads the built site back and reports dead
122
- links and invalid markup.
123
-
124
- None of this is required. A project that turns none of it on is a 0.3 project
125
- that happens to be running the 0.4.