Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .ci/acf-i18n-strings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#!/usr/bin/env php
<?php

/**
* Writes languages/acf-strings.php from acf-json/*.json.
*
* `wp i18n make-pot` cannot read ACF JSON, so this lists every translatable
* field string as an _x() call the .pot hook can extract. Run `composer i18n:acf`
* after changing ACF field groups, then commit both files.
*/

declare(strict_types=1);

$root = dirname(__DIR__);
// Same setting => context map as the runtime translator.
$textSettings = ['label' => 'label', 'instructions' => 'help text', 'placeholder' => 'field placeholder', 'prepend' => 'field affix', 'append' => 'field affix', 'message' => 'help text', 'button_label' => 'button label', 'ui_on_text' => 'label', 'ui_off_text' => 'label'];

/** @var array<string, array<string, array<string, true>>> $strings context => string => group titles */
$strings = [];

$collect = function (array $fields, string $group) use (&$collect, &$strings, $textSettings): void {
foreach ($fields as $field) {
foreach ($textSettings as $setting => $context) {
$value = $field[$setting] ?? '';
if (is_string($value) && trim($value) !== '') {
$strings[$context][$value][$group] = true;
}
}
foreach ((array) ($field['choices'] ?? []) as $label) {
if (is_string($label) && trim($label) !== '') {
$strings['label'][$label][$group] = true;
}
}
$collect($field['sub_fields'] ?? [], $group);
foreach ($field['layouts'] ?? [] as $layout) {
$collect($layout['sub_fields'] ?? [], $group);
}
}
};

$files = glob($root . '/acf-json/*.json') ?: [];
sort($files);
foreach ($files as $file) {
$data = json_decode((string) file_get_contents($file), true);
if (!is_array($data)) {
fwrite(STDERR, 'Skipping invalid JSON: ' . basename($file) . PHP_EOL);
continue;
}
$collect($data['fields'] ?? [], (string) ($data['title'] ?? basename($file, '.json')));
}

ksort($strings, SORT_STRING);

$out = "<?php\n\n"
. "/**\n"
. " * Generated by .ci/acf-i18n-strings.php from acf-json. Do not edit.\n"
. " * Never loaded; it only lets `wp i18n make-pot` extract ACF field strings.\n"
. " */\n\n"
. "defined('ABSPATH') || exit;\n\n"
. "return;\n";

// Same rule as the runtime translator: context only for 1 to 3 words.
$isShort = static function (string $text): bool {
$plain = preg_replace(['#<[^>]+>#', '#%(\d+\$)?[sdfu]#', '#&[a-z]+;#'], ' ', $text);

return preg_match_all("/[\p{L}\p{N}][\p{L}\p{N}'’.-]*/u", $plain) <= 3;
};

$count = 0;
$seen = [];
foreach ($strings as $context => $entries) {
ksort($entries, SORT_STRING);
foreach ($entries as $string => $groups) {
$string = (string) $string;
$short = $isShort($string);
$key = $short ? $context . "\4" . $string : $string;
$comment = str_replace('*/', '* /', 'ACF field group: ' . implode(', ', array_keys($groups)));
$call = $short
? "_x(" . var_export($string, true) . ', ' . var_export($context, true) . ", 'wicket-theme');"
: "__(" . var_export($string, true) . ", 'wicket-theme');";
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$out .= "\n/* translators: " . $comment . ". */\n" . $call . "\n";
$count++;
}
}

file_put_contents($root . '/languages/acf-strings.php', $out);
echo 'Wrote ' . $count . ' strings to languages/acf-strings.php' . PHP_EOL;
130 changes: 130 additions & 0 deletions .ci/hooks/update-pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env bash
#
# Pre-commit hook: regenerates languages/<text-domain>.pot from the staged files.
# Install: composer setup-hooks
# Uses `wp` on PATH, or downloads a pinned WP-CLI into the user cache on first use.
# The hook never blocks a commit: on any failure it warns and skips the .pot update.
#
# Manual and CI use:
# --force Regenerate even when no translatable files are staged.
# --strict Exit 1 on failure instead of skipping (for CI).
#

set -uo pipefail

EXCLUDE="node_modules,vendor,tests,.ci,docs,dist,build"
BUGS_URL="https://wicket.io"
TRIGGER='\.(php|js)$|(^|/)(block|theme)\.json$|(^|/)style\.css$'

WP_CLI_VERSION="2.12.0"
WP_CLI_SHA512="be928f6b8ca1e8dfb9d2f4b75a13aa4aee0896f8a9a0a1c45cd5d2c98605e6172e6d014dda2e27f88c98befc16c040cbb2bd1bfa121510ea5cdf5f6a30fe8832"
WP_CLI_URL="https://github.com/wp-cli/wp-cli/releases/download/v${WP_CLI_VERSION}/wp-cli-${WP_CLI_VERSION}.phar"
WP_CLI_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/wicket/wp-cli-${WP_CLI_VERSION}.phar"

FORCE=0
STRICT=0
for arg in "$@"; do
case "$arg" in
--force) FORCE=1 ;;
--strict) STRICT=1 ;;
esac
done

skip() {
echo "pre-commit: $1 Skipping .pot update." >&2
exit "$STRICT"
}

cd "$(git rev-parse --show-toplevel)" || skip "Not in a git repository."

if [ "$FORCE" -eq 0 ] && ! git diff --cached --name-only --diff-filter=ACMRD | grep -Eq "$TRIGGER"; then
exit 0
fi

sha512_of() {
if command -v sha512sum >/dev/null 2>&1; then
sha512sum "$1" | cut -d' ' -f1
else
shasum -a 512 "$1" | cut -d' ' -f1
fi
}

fetch_wp_cli() {
local part="${WP_CLI_CACHE}.part"
mkdir -p "$(dirname "$WP_CLI_CACHE")" || return 1
if command -v curl >/dev/null 2>&1; then
echo "pre-commit: downloading WP-CLI ${WP_CLI_VERSION} to ${WP_CLI_CACHE}" >&2
curl -fsSL --max-time 60 -o "$part" "$WP_CLI_URL" || return 1
elif command -v wget >/dev/null 2>&1; then
echo "pre-commit: downloading WP-CLI ${WP_CLI_VERSION} to ${WP_CLI_CACHE}" >&2
wget -q -T 60 -O "$part" "$WP_CLI_URL" || return 1
else
return 1
fi
if [ "$(sha512_of "$part")" != "$WP_CLI_SHA512" ]; then
rm -f "$part"
echo "pre-commit: WP-CLI download failed its checksum." >&2
return 1
fi
mv "$part" "$WP_CLI_CACHE"
}

if command -v wp >/dev/null 2>&1; then
WP=(wp)
else
command -v php >/dev/null 2>&1 || skip "PHP not found."
if [ ! -f "$WP_CLI_CACHE" ] || [ "$(sha512_of "$WP_CLI_CACHE")" != "$WP_CLI_SHA512" ]; then
fetch_wp_cli || skip "Could not download WP-CLI."
fi
WP=(php "$WP_CLI_CACHE")
fi

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT

# Optional package-specific excludes: comma-separated paths in .ci/pot-exclude.
if [ -f .ci/pot-exclude ]; then
extra="$(tr -d '[:space:]' < .ci/pot-exclude)"
[ -n "$extra" ] && EXCLUDE="${EXCLUDE},${extra}"
fi

# Generate from the staged snapshot, so unstaged edits stay out of the .pot.
# Excluded paths are left out of the snapshot, so large folders are not copied.
pathspec=()
IFS=',' read -ra excluded <<< "$EXCLUDE"
for path in "${excluded[@]}"; do
pathspec+=(":(exclude)${path}")
done
git ls-files -z -- . "${pathspec[@]}" | git checkout-index -z --stdin --prefix="$tmp/src/"

text_domain="$(grep -hm1 -E 'Text Domain:' "$tmp"/src/*.php "$tmp"/src/style.css 2>/dev/null \
| head -1 | sed -E 's/.*Text Domain:[[:space:]]*//; s/[[:space:]]*$//')"

if [ -z "$text_domain" ]; then
skip "No Text Domain header found."
fi

pot_file="languages/${text_domain}.pot"

if ! "${WP[@]}" i18n make-pot "$tmp/src" "$tmp/new.pot" \
--slug="$(basename "$PWD")" \
--domain="$text_domain" \
--exclude="$EXCLUDE" \
--no-location \
--headers="{\"Report-Msgid-Bugs-To\":\"${BUGS_URL}\"}" \
--quiet; then
skip "wp i18n make-pot failed."
fi

# Compare without the header block: it holds dates and tool versions that change on every run.
strip_header() { sed '1,/^$/d' "$1"; }

if [ -f "$pot_file" ] && git ls-files --error-unmatch "$pot_file" >/dev/null 2>&1 \
&& diff -q <(strip_header "$pot_file") <(strip_header "$tmp/new.pot") >/dev/null; then
exit 0
fi

mkdir -p languages
cp "$tmp/new.pot" "$pot_file"
git add "$pot_file"
echo "pre-commit: updated ${pot_file}"
1 change: 1 addition & 0 deletions .ci/pot-exclude
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
font-awesome
33 changes: 33 additions & 0 deletions .github/workflows/i18n-pot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Translation template check

# Fails the PR when languages/<text-domain>.pot does not match the source.
# Uses the same script as the pre-commit hook, so both generate the same file.

on:
pull_request:

permissions:
contents: read

jobs:
pot:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'

- name: Regenerate the .pot
run: bash .ci/hooks/update-pot --force --strict

- name: Fail when the committed .pot is out of date
run: |
if ! git diff --cached --quiet -- languages/; then
echo "::error::languages/*.pot is out of date. Commit through the pre-commit hook (composer setup-hooks), or run: bash .ci/hooks/update-pot --force"
git diff --cached --stat -- languages/
exit 1
fi
6 changes: 3 additions & 3 deletions 404.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
<div class="container">
<div class="page-not-found" >
<h1 class="page-not-found__title" >404</h1>
<h2 class="page-not-found__subtitle" ><?php _e('Page not found', 'wicket'); ?></h2>
<h2 class="page-not-found__subtitle" ><?php _ex('Page not found', 'label', 'wicket-theme'); ?></h2>
<div class="page-not-found__content" >
<p><?php _e("The page you're looking for cannot be found.", 'wicket'); ?></p>
<p><?php _e("The page you're looking for cannot be found.", 'wicket-theme'); ?></p>
</div>
<?php get_component('button', [
'variant' => 'secondary',
'a_tag' => true,
'label' => __('Go back to the Homepage', 'wicket'),
'label' => __('Go back to the Homepage', 'wicket-theme'),
'link' => home_url(),
]) ?>
</div>
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This repository is a WordPress theme (`wicket-wp-theme`).
- `composer test:unit` runs unit tests only.
- `composer lint` checks PHP formatting (`php-cs-fixer --dry-run --diff`).
- `composer format` applies PHP formatting fixes.
- `composer i18n:acf` regenerates `languages/acf-strings.php` after changes to `acf-json/`, so ACF field strings reach the `.pot`. Commit both.
- `composer setup-hooks` installs `.ci/pre-push`.

## Coding Style & Naming Conventions
Expand Down
2 changes: 1 addition & 1 deletion acf-json/group_6595533d4b282.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
"wpml_cf_preferences": 1,
"choices": {
"left": "Left",
"center": "Center"
"center": "Centre"
},
"default_value": "left",
"return_format": "value",
Expand Down
2 changes: 1 addition & 1 deletion acf-json/group_65bcc91687327.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
},
{
"key": "field_68665fae6f84e",
"label": "Title color",
"label": "Title colour",
"name": "featured_posts_title_color",
"aria-label": "",
"type": "color_picker",
Expand Down
2 changes: 1 addition & 1 deletion acf-json/group_66f2d49c4ce0d.json
Original file line number Diff line number Diff line change
Expand Up @@ -3053,7 +3053,7 @@
"name": "drop-shadows",
"aria-label": "",
"type": "group",
"instructions": "Format: X_position Y_position Blur Color",
"instructions": "Format: X_position Y_position Blur Colour",
"required": 0,
"conditional_logic": 0,
"wrapper": {
Expand Down
1 change: 1 addition & 0 deletions blocks/accordion/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/accordion",
"textdomain": "wicket-theme",
"title": "Accordion",
"description": "A Wicket block for an accordion",
"style": "block-wicket-accordion",
Expand Down
1 change: 1 addition & 0 deletions blocks/author/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/author",
"textdomain": "wicket-theme",
"title": "Author",
"description": "A Wicket block for author",
"style": "block-wicket-author",
Expand Down
1 change: 1 addition & 0 deletions blocks/banner/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/banner",
"textdomain": "wicket-theme",
"title": "Banner",
"description": "A Wicket block for banner",
"style": "block-wicket-banner",
Expand Down
1 change: 1 addition & 0 deletions blocks/call-out-card/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/call-out-card",
"textdomain": "wicket-theme",
"title": "Call Out Card",
"description": "A Wicket block for call out card",
"style": "block-wicket-call-out-card",
Expand Down
1 change: 1 addition & 0 deletions blocks/card/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/card",
"textdomain": "wicket-theme",
"title": "Card",
"description": "A Wicket block for card",
"style": "block-wicket-card",
Expand Down
1 change: 1 addition & 0 deletions blocks/contact-card/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/contact-card",
"textdomain": "wicket-theme",
"title": "Contact Card",
"description": "A Wicket block for contact card",
"style": "block-wicket-contact-card",
Expand Down
1 change: 1 addition & 0 deletions blocks/dynamically-related-content/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/dynamically-related-content",
"textdomain": "wicket-theme",
"title": "Dynamically Related Content",
"description": "A Wicket block for dynamically related content.",
"style": "block-wicket-dynamically-related-content",
Expand Down
1 change: 1 addition & 0 deletions blocks/dynamically-related-events/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/dynamically-related-events",
"textdomain": "wicket-theme",
"title": "Dynamically Related Events",
"description": "A Wicket block for dynamically related events.",
"style": "block-wicket-dynamically-related-events",
Expand Down
1 change: 1 addition & 0 deletions blocks/featured-posts/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/featured-posts",
"textdomain": "wicket-theme",
"title": "Featured Posts",
"description": "A Wicket block for featured posts",
"style": "block-wicket-featured-posts",
Expand Down
4 changes: 2 additions & 2 deletions blocks/featured-posts/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function init($block = [])
if (!$posts) {
$output = '<div ' . $placeholder_styles . '>';
if (is_admin()) {
$output .= '<p>' . __('Use the Block controls in edit mode or on the right to add featured posts.', 'wicket') . '</p>';
$output .= '<p>' . __('Use the Block controls in edit mode or on the right to add featured posts.', 'wicket-theme') . '</p>';
}
$output .= '</div>';
echo $output;
Expand All @@ -40,7 +40,7 @@ function init($block = [])

echo '<div ' . $attrs . ' ' . $placeholder_styles . '>';
if (is_admin() && empty($posts)) {
echo '<p>' . __('Use the Block controls in edit mode or on the right to add featured posts.', 'wicket') . '</p>';
echo '<p>' . __('Use the Block controls in edit mode or on the right to add featured posts.', 'wicket-theme') . '</p>';
}
get_component('featured-posts', [
'title' => $title,
Expand Down
1 change: 1 addition & 0 deletions blocks/icon-grid/block.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"name": "wicket/icongrid",
"textdomain": "wicket-theme",
"title": "Icon Grid",
"description": "A Wicket block for icon grid",
"style": "block-wicket-icon-grid",
Expand Down
Loading
Loading