编辑文件:.make-pot.php
<?php /** * Regenerate clothb2b.pot from source. * * Scans the child theme PHP files (functions.php, front-page.php, * template-parts/*.php) and the mu-plugin for gettext calls bound to the * `clothb2b` text domain, plus pll_register_string() registrations, and writes * a GNU gettext .pot with accurate file:line references. Keeps the catalogue in * sync when strings are added/removed without needing xgettext on the host. * * Usage: php .make-pot.php * * Notes: * - Only literal first-argument strings are captured; dynamic args such as * __( $item->title, 'clothb2b' ) are intentionally skipped (they are not * cataloguable here — their values come from runtime data). * - Duplicate msgids across files merge their references, like xgettext. * * @package ClothB2B_Child */ if ( PHP_SAPI !== 'cli' ) { exit( 'CLI only.' ); } $theme_dir = dirname( __DIR__ ); // .../themes/astra-child. /** * Files to scan, keyed by the display name used in #: references. * * @var array<string,string> */ $files = array( 'functions.php' => $theme_dir . '/functions.php', 'front-page.php' => $theme_dir . '/front-page.php', 'mu-plugins/clothb2b-catalog.php' => $theme_dir . '/../../mu-plugins/clothb2b-catalog.php', ); foreach ( glob( $theme_dir . '/template-parts/*.php' ) as $part ) { $files[ 'template-parts/' . basename( $part ) ] = $part; } // Design-system includes (runtime overrides split out of functions.php). foreach ( glob( $theme_dir . '/inc/*.php' ) as $inc ) { $files[ 'inc/' . basename( $inc ) ] = $inc; } // Bespoke inner-page templates (Who We Are / FAQ / Inquiry / Full-Width). foreach ( glob( $theme_dir . '/page-templates/*.php' ) as $tpl ) { $files[ 'page-templates/' . basename( $tpl ) ] = $tpl; } // Homepage section partials (composed by front-page.php). foreach ( glob( $theme_dir . '/template-parts/home/*.php' ) as $part ) { $files[ 'template-parts/home/' . basename( $part ) ] = $part; } /** * Patterns: capture the cataloguable literal string from each call style. * Group 1 = the msgid. The domain must be 'clothb2b' for the gettext family. * * @var array<string,string> */ $patterns = array( // __/esc_html__/esc_attr__/_x etc. with the clothb2b domain: ( 'msgid', 'clothb2b' … ) 'gettext' => "/(?:__|_e|esc_html_e|esc_html__|esc_attr_e|esc_attr__|_ex|_nx|_n)\s*\(\s*'((?:[^'\\\\]|\\\\.)*)'\s*,\s*'clothb2b'/", // _x / esc_html_x / _ex: ( 'msgid', 'context', 'clothb2b' ) — capture first arg only. 'gettext_context' => "/(?:_x|esc_html_x|esc_attr_x|_ex)\s*\(\s*'((?:[^'\\\\]|\\\\.)*)'\s*,\s*'[^']*'\s*,\s*'clothb2b'/", // pll_register_string( 'clothb2b', 'msgid' … ) — capture the 2nd arg. 'pll_register' => "/pll_register_string\s*\(\s*'[^']*'\s*,\s*'((?:[^'\\\\]|\\\\.)*)'/", ); /** * @var array<string,string[]> msgid => list of "file:line" refs. */ $entries = array(); foreach ( $files as $display => $path ) { if ( ! is_file( $path ) ) { continue; } $lines = file( $path, FILE_IGNORE_NEW_LINES ); if ( false === $lines ) { continue; } foreach ( $lines as $i => $line ) { $stripped = ltrim( $line ); // Skip PHP / doc-block comment lines so example calls inside comments // (e.g. the i18n header that mentions esc_html_e( '…', 'clothb2b' )) // are not mistaken for real strings. if ( '' === $stripped || '*' === $stripped[0] || '/' === $stripped[0] || '#' === $stripped[0] ) { continue; } foreach ( $patterns as $pattern ) { if ( preg_match_all( $pattern, $line, $m ) ) { foreach ( $m[1] as $msgid_raw ) { // Unescape gettext \\ -> \ , \' -> ' so the .pot holds real chars. $msgid = stripcslashes( $msgid_raw ); if ( '' === $msgid ) { continue; } $ref = $display . ':' . ( $i + 1 ); $entries[ $msgid ][] = $ref; } } } } } ksort( $entries ); /** * Dynamic UI labels that cannot be catalogued from source (their values are * runtime page/category titles passed through __()). Append them so translators * see the full set; extend this list when new menu items appear. * * @var string[] */ $dynamic_labels = array( 'B2B Entrance', 'FAQ', 'News', 'Who We Are', 'Shop', 'Home', 'Cart', 'Checkout', 'My account', 'Women', 'Man', 'Kids', ); foreach ( $dynamic_labels as $label ) { if ( ! isset( $entries[ $label ] ) ) { $entries[ $label ] = array( 'functions.php (dynamic menu label)' ); } } /** * Escape a string for .po/.pot. * * @param string $s Raw. * @return string */ function cb_pot_escape( $s ) { $from = array( '\\', "\n", "\r", "\t", '"' ); $to = array( '\\\\', '\\n', '\\r', '\\t', '\\"' ); return str_replace( $from, $to, $s ); } $out = "# ClothB2B Child translation template.\n"; $out .= "# Copyright (C) 2026 ClothB2B Child\n"; $out .= '# This file is distributed under the same license as the ClothB2B Child theme.' . "\n"; $out .= "msgid \"\"\n"; $out .= "msgstr \"\"\n"; $out .= "\"Project-Id-Version: ClothB2B Child 1.0.0\\n\"\n"; $out .= "\"Report-Msgid-Bugs-To: https://example.com/support\\n\"\n"; $out .= "\"MIME-Version: 1.0\\n\"\n"; $out .= "\"Content-Type: text/plain; charset=UTF-8\\n\"\n"; $out .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; $out .= "\"POT-Creation-Date: " . gmdate( 'Y-m-d\TH:i:s+00:00' ) . "\\n\"\n"; $out .= "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n"; $out .= "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n"; $out .= "\"Language-Team: ClothB2B <info@example.com>\\n\"\n"; $out .= "\"Language: \\n\"\n"; $out .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n"; $out .= "\"X-Domain: clothb2b\\n\"\n"; $out .= "\n"; foreach ( $entries as $msgid => $refs ) { $out .= '#: ' . implode( ' ', array_unique( $refs ) ) . "\n"; $out .= 'msgid "' . cb_pot_escape( $msgid ) . "\"\n"; $out .= "msgstr \"\"\n\n"; } $path = $theme_dir . '/languages/clothb2b.pot'; $bytes = file_put_contents( $path, $out ); if ( false === $bytes ) { fwrite( STDERR, "Cannot write $path\n" ); exit( 1 ); } echo 'wrote ' . $path . ' (' . $bytes . ' bytes, ' . count( $entries ) . " msgids)\n";
保存
返回