Verwendung von Markdown und Lokalisierung im WordPress Block Editor

Avatar of Leonardo Losoviz
Leonardo Losoviz am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Wenn wir dem Benutzer Dokumentation direkt im WordPress Editor anzeigen müssen, wie machen wir das am besten?

Da der Block-Editor auf React basiert, sind wir vielleicht versucht, React-Komponenten und HTML-Code für die Dokumentation zu verwenden. Diesen Ansatz habe ich in meinem vorherigen Artikel verfolgt, der eine Möglichkeit gezeigt hat, Dokumentation in einem Modalfenster anzuzeigen.

Aber diese Lösung ist nicht fehlerfrei, da die Hinzufügung von Dokumentation durch React-Komponenten und HTML-Code sehr umständlich und schwer zu pflegen sein kann. Zum Beispiel enthält das Modal aus dem obigen Bild die Dokumentation in einer React-Komponente wie dieser

const CacheControlDescription = () => {
  return (
    <p>The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or <code>no-store</code> if the max-age is 0</p>
  )
}

Markdown anstelle von HTML zu verwenden, kann die Aufgabe erleichtern. Zum Beispiel könnte die obige Dokumentation aus der React-Komponente entfernt und in eine Markdown-Datei wie /docs/cache-control.md verschoben werden.

The Cache-Control header will contain the minimum max-age value from all fields/directives involved in the request, or `no-store` if the max-age is 0

Was sind die Vor- und Nachteile der Verwendung von Markdown im Vergleich zu reinem HTML?

VorteileNachteile
✅ Markdown zu schreiben ist einfacher und schneller als HTML❌ Die Dokumentation kann keine React-Komponenten enthalten
✅ Die Dokumentation kann vom Quellcode des Blocks getrennt gehalten werden (sogar in einem separaten Repository)❌ Wir können die __ Funktion (die hilft, den Inhalt durch .po Dateien zu lokalisieren) nicht zur Ausgabe von Text verwenden
✅ Copy-Editoren können die Dokumentation ändern, ohne Angst haben zu müssen, den Code zu beschädigen
✅ Der Dokumentationscode wird nicht zum JavaScript-Asset des Blocks hinzugefügt, das dann schneller geladen werden kann

Was die Nachteile betrifft, so ist die Unmöglichkeit, React-Komponenten zu verwenden, zumindest für einfache Dokumentationen kein Problem. Der Mangel an Lokalisierung ist jedoch ein großes Problem. Text in der React-Komponente, der über die JavaScript-Funktion __ hinzugefügt wird, kann extrahiert und mit Übersetzungen aus POT-Dateien ersetzt werden. Inhalt in Markdown kann auf diese Funktionalität nicht zugreifen.

Die Unterstützung der Lokalisierung für Dokumentationen ist obligatorisch, daher müssen wir dies ausgleichen. In diesem Artikel werden wir zwei Ziele verfolgen

  • Verwendung von Markdown zum Schreiben von Dokumentationen (dargestellt durch einen Block des WordPress-Editors)
  • Übersetzung der Dokumentation in die Sprache des Benutzers

Legen wir los!

Laden von Markdown-Inhalten

Nachdem wir eine Markdown-Datei /docs/cache-control.md erstellt haben, können wir ihren Inhalt (bereits als HTML gerendert) importieren und in die React-Komponente wie folgt einfügen

import CacheControlDocumentation from '../docs/cache-control.md';


const CacheControlDescription = () => {
  return (
    <div
      dangerouslySetInnerHTML={ { __html: CacheControlDocumentation } }
    />
  );
}

Diese Lösung basiert auf webpack, dem Modul-Bundler im Kern des WordPress-Editors.

Bitte beachten Sie, dass der WordPress-Editor derzeit webpack 4.42 verwendet. Die obige Dokumentation auf der webpack-Website entspricht jedoch Version 5 (die noch in der Beta-Phase ist). Die Dokumentation für Version 4 befindet sich auf einer Subseite.

Der Inhalt wird über die Loader von webpack von Markdown in HTML umgewandelt. Dazu muss der Block seine webpack-Konfiguration anpassen und Regeln hinzufügen, um markdown-loader und html-loader zu verwenden.

Fügen Sie dazu eine Datei, webpack.config.js, am Stammverzeichnis des Blocks mit diesem Code hinzu

// This is the default webpack configuration from Gutenberg
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );


// Customize adding the required rules for the block
module.exports = {
  ...defaultConfig,
  module: {
    ...defaultConfig.module,
    rules: [
      ...defaultConfig.module.rules,
      {
        test: /\.md$/,
        use: [
          {
            loader: "html-loader"
          },
          {
            loader: "markdown-loader"
          }
        ]
      }
    ],
  },
};

Und installieren Sie die entsprechenden Pakete

npm install --save-dev markdown-loader html-loader

Lassen Sie uns dabei eine kleine Verbesserung vornehmen. Der Docs-Ordner könnte die Dokumentation für Komponenten enthalten, die sich irgendwo im Projekt befinden. Um die Berechnung des relativen Pfads von jeder Komponente zu diesem Ordner zu vermeiden, können wir einen Alias, @docs, in webpack.config.js hinzufügen, der auf den Ordner /docs verweist.

const path = require( 'path' );
config.resolve.alias[ '@docs' ] = path.resolve( process.cwd(), 'docs/' )

Jetzt sind die Imports vereinfacht

import CacheControlDocumentation from '@docs/cache-control.md';

Das war's! Wir können jetzt Dokumentation aus externen Markdown-Dateien in die React-Komponente einfügen.

Übersetzung der Dokumentation in die Sprache des Benutzers

Wir können Strings nicht über .po-Dateien für Markdown-Inhalte übersetzen, aber es gibt eine Alternative: verschiedene Markdown-Dateien für verschiedene Sprachen erstellen. Anstatt einer einzigen Datei (/docs/cache-control.md) haben wir dann eine Datei pro Sprache, die jeweils unter dem entsprechenden Sprachcode gespeichert ist

  • /docs/en/cache-control.md
  • /docs/fr/cache-control.md
  • /docs/zh/cache-control.md
  • usw.

Wir könnten auch Übersetzungen für Sprache *und* Region unterstützen, sodass amerikanisches und britisches Englisch unterschiedliche Versionen haben können, und auf die sprachliche Version zurückgreifen, wenn keine regionale Übersetzung vorhanden ist (z.B. "en_CA" wird von "en" behandelt)

  • /docs/en_US/cache-control.md
  • /docs/en_GB/cache-control.md
  • /docs/en/cache-control.md

Um die Sache zu vereinfachen, erkläre ich nur, wie verschiedene Sprachen ohne Regionen unterstützt werden. Der Code ist aber weitgehend derselbe.

Der in diesem Artikel vorgestellte Code kann auch im Quellcode eines von mir erstellten WordPress-Plugins eingesehen werden.

Übergabe der Benutzersprache an den Block

Die Sprache des Benutzers in WordPress kann von get_locale() abgerufen werden. Da der Locale den Sprachcode und die Region (wie z. B. "en_US") enthält, parsen wir ihn, um den Sprachcode selbst zu extrahieren.

function get_locale_language(): string 
{
  $localeParts = explode( '_', get_locale() );
  return $localeParts[0];
}

Über wp_localize_script() stellen wir den Sprachcode dem Block zur Verfügung, als userLang-Eigenschaft unter einer globalen Variable (in diesem Fall graphqlApiCacheControl).

// The block was registered as $blockScriptRegistrationName
wp_localize_script(
  $blockScriptRegistrationName,
  'graphqlApiCacheControl',
  [
    'userLang' => get_locale_language(),
  ]
);

Jetzt ist der Sprachcode des Benutzers für den Block verfügbar.

const lang = window.graphqlApiCacheControl.userLang; 

Dynamische Importe

Wir können die Sprache des Benutzers erst zur Laufzeit kennen. Die import-Anweisung ist jedoch statisch, nicht dynamisch. Daher können wir dies nicht tun

// `lang` contains the user's language
import CacheControlDocumentation from '@docs/${ lang }/cache-control.md';

Das gesagt, webpack ermöglicht es uns, Module dynamisch über die import-Funktion zu laden, die standardmäßig das angeforderte Modul in einen separaten Chunk aufteilt (d. h. es ist nicht in der Hauptkompilierung build/index.js-Datei enthalten), um verzögert geladen zu werden.

Dieses Verhalten eignet sich zum Anzeigen von Dokumentation in einem Modalfenster, das durch eine Benutzeraktion ausgelöst und nicht im Voraus geladen wird. import muss Informationen darüber enthalten, wo sich das Modul befindet, daher funktioniert dieser Code

import( `@docs/${ lang }/cache-control.md` ).then( module => {
  // ...
});

Aber dieser scheinbar ähnliche Code tut es nicht.

const dynamicModule = `@docs/${ lang }/cache-control.md`
import( dynamicModule ).then( module => {
  // ...
});

Der Inhalt der Datei ist unter dem Schlüssel default des importierten Objekts zugänglich.

const cacheControlContent = import( `@docs/${ lang }/cache-control.md` ).then( obj => obj.default )

Wir können diese Logik in eine Funktion namens getMarkdownContent verallgemeinern und den Namen der Markdown-Datei zusammen mit der Sprache übergeben.

const getMarkdownContent = ( fileName, lang ) => {
  return import( `@docs/${ lang }/${ fileName }.md` )
    .then( obj => obj.default )
} 

Verwaltung der Chunks

Um die Block-Assets organisiert zu halten, behalten wir die Dokumentations-Chunks im Unterordner /docs (der im Ordner build/ erstellt wird) und geben ihnen beschreibende Dateinamen.

Dann, mit zwei Dokumenten (cache-control.md und cache-purging.md) in drei Sprachen (Englisch, Französisch und Mandarin-Chinesisch), werden die folgenden Chunks erzeugt

  • build/docs/en-cache-control-md.js
  • build/docs/fr-cache-control-md.js
  • build/docs/zh-cache-control-md.js
  • build/docs/en-cache-purging-md.js
  • build/docs/fr-cache-purging-md.js
  • build/docs/zh-cache-purging-md.js

Dies wird durch die Verwendung des Magic Comment /* webpackChunkName: "docs/[request]" */ direkt vor dem import-Argument erreicht.

const getMarkdownContent = ( fileName, lang ) => {
  return import( /* webpackChunkName: "docs/[request]" */ `@docs/${ lang }/${ fileName }.md` )
    .then(obj => obj.default)
} 

Festlegen des öffentlichen Pfads für die Chunks

webpack weiß dank der publicPath-Konfigurationsoption, wo die Chunks abgerufen werden sollen. Wenn sie nicht angegeben ist, wird die aktuelle URL des WordPress-Editors, /wp-admin/, verwendet, was zu einem 404 führt, da die Chunks woanders liegen. Für meinen Block befinden sie sich unter /wp-content/plugins/graphql-api/blocks/cache-control/build/.

Wenn der Block für den Eigengebrauch bestimmt ist, können wir publicPath in webpack.config.js fest codieren oder ihn über eine ASSET_PATH-Umgebungsvariable bereitstellen. Andernfalls müssen wir den öffentlichen Pfad zur Laufzeit an den Block übergeben. Dazu berechnen wir die URL für den build/-Ordner des Blocks.

$blockPublicPath = plugin_dir_url( __FILE__ ) . '/blocks/cache-control/build/';

Dann injizieren wir ihn auf der JavaScript-Seite, indem wir den Block lokalisieren.

// The block was registered as $blockScriptRegistrationName
wp_localize_script(
    $blockScriptRegistrationName,
    'graphqlApiCacheControl',
    [
      //...
      'publicPath' => $blockPublicPath,
    ]
);

Und dann stellen wir den öffentlichen Pfad der JavaScript-Variable __webpack_public_path__ zur Verfügung.

__webpack_public_path__ = window.graphqlApiCacheControl.publicPath;

Ausweichen auf eine Standardsprache

Was passiert, wenn keine Übersetzung für die Sprache des Benutzers vorhanden ist? In diesem Fall löst der Aufruf von getMarkdownContent einen Fehler aus.

Wenn die Sprache beispielsweise auf Deutsch eingestellt ist, wird die Browserkonsole Folgendes anzeigen:

Uncaught (in promise) Error: Cannot find module './de/cache-control.md'

Die Lösung besteht darin, den Fehler abzufangen und dann den Inhalt in einer Standardsprache zurückzugeben, die vom Block immer erfüllt wird.

const getMarkdownContentOrUseDefault = ( fileName, defaultLang, lang ) => {
  return getMarkdownContent( fileName, lang )
    .catch( err => getMarkdownContent( fileName, defaultLang ) )
}

Bitte beachten Sie das unterschiedliche Verhalten bei der Kodierung von Dokumentation als HTML innerhalb der React-Komponente und als externe Markdown-Datei, wenn die Übersetzung unvollständig ist. Im ersten Fall, wenn eine Zeichenfolge übersetzt wurde, eine andere aber nicht (in der .po-Datei), wird die React-Komponente am Ende gemischte Sprachen anzeigen. Im zweiten Fall ist es alles oder nichts: Entweder ist die Dokumentation vollständig übersetzt oder nicht. 

Festlegen der Dokumentation im Modal

Bis jetzt können wir die Dokumentation aus der Markdown-Datei abrufen. Sehen wir uns an, wie sie im Modal angezeigt wird.

Wir umschließen zuerst die Modal-Komponente von Gutenberg, um den Inhalt als HTML einzufügen.

import { Modal } from '@wordpress/components';


const ContentModal = ( props ) => {
  const { content } = props;
  return (
    <Modal 
      { ...props }
    >
      <div
        dangerouslySetInnerHTML={ { __html: content } }
      />
    </Modal>
  );
};

Dann rufen wir den Inhalt aus der Markdown-Datei ab und übergeben ihn als Prop an das Modal, indem wir einen State Hook namens page verwenden. Das dynamische Laden von Inhalten ist eine asynchrone Operation, daher müssen wir auch einen Effect Hook verwenden, um einen Seiteneffekt in der Komponente durchzuführen. Wir müssen den Inhalt nur einmal aus der Markdown-Datei lesen, daher übergeben wir ein leeres Array als zweiten Argument an useEffect (andernfalls würde der Hook immer wieder ausgelöst werden).

import { useState, useEffect } from '@wordpress/element';

const CacheControlContentModal = ( props ) => {
  const fileName = 'cache-control'
  const lang = window.graphqlApiCacheControl.userLang
  const defaultLang = 'en'


  const [ page, setPage ] = useState( [] );


  useEffect(() => {
    getMarkdownContentOrUseDefault( fileName, defaultLang, lang ).then( value => {
      setPage( value )
    });
  }, [] );


  return (
    <ContentModal
      { ...props }
      content={ page }
    />
  );
};

Sehen wir uns das in Aktion an. Bitte beachten Sie, wie der Chunk, der die Dokumentation enthält, verzögert geladen wird (d. h. er wird ausgelöst, wenn der Block bearbeitet wird).

Tadaaaaaaaa 🎉

Das Schreiben von Dokumentationen ist vielleicht nicht Ihre Lieblingsbeschäftigung, aber wenn es einfach zu schreiben und zu pflegen ist, kann es den Schmerz lindern.

Markdown anstelle von reinem HTML zu verwenden ist sicherlich ein Weg dorthin. Ich hoffe, der gerade behandelte Ansatz verbessert nicht nur Ihren Workflow, sondern bietet auch Ihren WordPress-Benutzern eine schöne Verbesserung.