Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## 0.6.0 (May 11, 2026)

### Added
- **Language** field on the content plugin and site module — forces the widget UI language for the rendered widget. Leave empty to auto-detect from the visitor's browser. Supported codes: `en`, `es`, `fr`, `de`, `pt`, `it`, `sv`, `da`, `el`, `fa`, `pl`, `ru`, `ja`. Per-shortcode override on the content plugin via `language="…"`. `widget_config` JSON `language` key still wins over both. Unsupported codes are silently dropped.

## 0.5.0 (May 11, 2026)

### Added
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ By default, the plugin reads its settings (server URL, service body IDs, etc.) f
{crumb server="https://bmlt.example.org/main_server" service_body="42" view="map"}
```

Recognised shortcode args: `server`, `service_body`, `format_ids`, `view` (`list`/`map`), `geolocation` (`true`/`false`), `geolocation_radius`, `update_url`, `columns`.
Recognised shortcode args: `server`, `service_body`, `format_ids`, `view` (`list`/`map`), `language` (e.g. `es`), `geolocation` (`true`/`false`), `geolocation_radius`, `update_url`, `columns`.

### In a module position (module)

Expand All @@ -54,6 +54,7 @@ Both the plugin and the module expose the same fields:
| **BMLT Server URL** | Required. Full URL to your BMLT server (e.g. `https://your-server/main_server`). |
| **Service Body IDs** | Optional. Single ID or comma-separated list. Empty = all meetings. Child service bodies are always included. |
| **Default View** | `list` or `map`. Overridable per shortcode and via `?view=` query param. |
| **Language** | Optional. Forces the widget UI language (`en`, `es`, `fr`, `de`, `pt`, `it`, `sv`, `da`, `el`, `fa`, `pl`, `ru`, `ja`). Empty = auto-detect from browser. Overridable per shortcode on the content plugin. |
| **CSS Template** | `Full Width` or `Full Width (Force Viewport)` for breaking out of narrow content areas. |
| **Base Path for Pretty URLs** | e.g. `meetings` → `/meetings/monday-night-meeting-42` (History API routing). Empty = hash-based routing. |
| **Update Meeting URL** | Optional. URL template for the "Update Meeting Info" link on the meeting detail panel. Tokens: `{meeting_id}`, `{meeting_name}`, `{server_url}`, `{return_url}`. Works with bmlt-workflow, hosted forms, or `mailto:` URLs. |
Expand Down
4 changes: 4 additions & 0 deletions packages/module/language/en-GB/mod_crumb.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ MOD_CRUMB_FIELD_VIEW_DEFAULT="— Widget Default (list) —"
MOD_CRUMB_FIELD_VIEW_LIST="List"
MOD_CRUMB_FIELD_VIEW_MAP="Map"

MOD_CRUMB_FIELD_LANGUAGE_LABEL="Language"
MOD_CRUMB_FIELD_LANGUAGE_DESC="Optional. Forces the widget UI language. Default behavior is to detect from the visitor's browser."
MOD_CRUMB_FIELD_LANGUAGE_DEFAULT="— Auto-detect from browser —"

MOD_CRUMB_FIELD_CSS_TEMPLATE_LABEL="CSS Template"
MOD_CRUMB_FIELD_CSS_TEMPLATE_DESC="Full Width fits the content area. Full Width (Force Viewport) breaks out to span the full browser width."
MOD_CRUMB_FIELD_CSS_TEMPLATE_NONE="— None —"
Expand Down
24 changes: 23 additions & 1 deletion packages/module/mod_crumb.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<license>GPL v2 or later</license>
<authorEmail>bmlt-enabled@googlegroups.com</authorEmail>
<authorUrl>https://github.com/bmlt-enabled/crumb-joomla</authorUrl>
<version>0.5.0</version>
<version>0.6.0</version>
<description>MOD_CRUMB_XML_DESCRIPTION</description>
<namespace path="src">BmltEnabled\Module\Crumb\Site</namespace>

Expand Down Expand Up @@ -63,6 +63,28 @@
<option value="list">MOD_CRUMB_FIELD_VIEW_LIST</option>
<option value="map">MOD_CRUMB_FIELD_VIEW_MAP</option>
</field>
<field
name="language"
type="list"
label="MOD_CRUMB_FIELD_LANGUAGE_LABEL"
description="MOD_CRUMB_FIELD_LANGUAGE_DESC"
default=""
>
<option value="">MOD_CRUMB_FIELD_LANGUAGE_DEFAULT</option>
<option value="en">English (en)</option>
<option value="es">Español (es)</option>
<option value="fr">Français (fr)</option>
<option value="de">Deutsch (de)</option>
<option value="pt">Português (pt)</option>
<option value="it">Italiano (it)</option>
<option value="sv">Svenska (sv)</option>
<option value="da">Dansk (da)</option>
<option value="el">Ελληνικά (el)</option>
<option value="fa">فارسی (fa)</option>
<option value="pl">Polski (pl)</option>
<option value="ru">Русский (ru)</option>
<option value="ja">日本語 (ja)</option>
</field>
<field
name="css_template"
type="list"
Expand Down
13 changes: 13 additions & 0 deletions packages/module/src/Helper/CrumbHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class CrumbHelper
{
public const ALLOWED_VIEWS = ['list', 'map'];

/** Languages the widget supports (mirrors src/stores/localization.ts). */
public const SUPPORTED_LANGUAGES = ['en', 'es', 'fr', 'de', 'pt', 'it', 'sv', 'da', 'el', 'fa', 'pl', 'ru', 'ja'];

/**
* @return array<string,string>
*/
Expand All @@ -32,6 +35,7 @@ public function getSettings(Registry $params): array
'service_body' => (string) $params->get('service_body', ''),
'format_ids' => (string) $params->get('format_ids', ''),
'view' => (string) $params->get('view', ''),
'language' => (string) $params->get('language', ''),
'css_template' => (string) $params->get('css_template', ''),
'base_path' => (string) $params->get('base_path', ''),
'geolocation_radius' => (string) $params->get('geolocation_radius', ''),
Expand Down Expand Up @@ -113,6 +117,15 @@ public function render(array $settings): string
}
}

// Language: saved module param fills in if widget_config JSON didn't set one.
// Unsupported codes are silently dropped (widget auto-detects from navigator.language).
if (!isset($configArray['language'])) {
$langSetting = strtolower(trim((string) ($settings['language'] ?? '')));
if ($langSetting !== '' && \in_array($langSetting, self::SUPPORTED_LANGUAGES, true)) {
$configArray['language'] = $langSetting;
}
}

$configScript = '';
if (!empty($configArray)) {
$configScript = '<script>window.CrumbWidgetConfig = '
Expand Down
24 changes: 23 additions & 1 deletion packages/plugin/crumb.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<license>GPL v2 or later</license>
<authorEmail>bmlt-enabled@googlegroups.com</authorEmail>
<authorUrl>https://github.com/bmlt-enabled/crumb-joomla</authorUrl>
<version>0.5.0</version>
<version>0.6.0</version>
<description>PLG_CONTENT_CRUMB_XML_DESCRIPTION</description>
<namespace path="src">BmltEnabled\Plugin\Content\Crumb</namespace>

Expand Down Expand Up @@ -62,6 +62,28 @@
<option value="list">PLG_CONTENT_CRUMB_FIELD_VIEW_LIST</option>
<option value="map">PLG_CONTENT_CRUMB_FIELD_VIEW_MAP</option>
</field>
<field
name="language"
type="list"
label="PLG_CONTENT_CRUMB_FIELD_LANGUAGE_LABEL"
description="PLG_CONTENT_CRUMB_FIELD_LANGUAGE_DESC"
default=""
>
<option value="">PLG_CONTENT_CRUMB_FIELD_LANGUAGE_DEFAULT</option>
<option value="en">English (en)</option>
<option value="es">Español (es)</option>
<option value="fr">Français (fr)</option>
<option value="de">Deutsch (de)</option>
<option value="pt">Português (pt)</option>
<option value="it">Italiano (it)</option>
<option value="sv">Svenska (sv)</option>
<option value="da">Dansk (da)</option>
<option value="el">Ελληνικά (el)</option>
<option value="fa">فارسی (fa)</option>
<option value="pl">Polski (pl)</option>
<option value="ru">Русский (ru)</option>
<option value="ja">日本語 (ja)</option>
</field>
<field
name="css_template"
type="list"
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin/language/en-GB/plg_content_crumb.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ PLG_CONTENT_CRUMB_FIELD_VIEW_DEFAULT="— Widget Default (list) —"
PLG_CONTENT_CRUMB_FIELD_VIEW_LIST="List"
PLG_CONTENT_CRUMB_FIELD_VIEW_MAP="Map"

PLG_CONTENT_CRUMB_FIELD_LANGUAGE_LABEL="Language"
PLG_CONTENT_CRUMB_FIELD_LANGUAGE_DESC="Optional. Forces the widget UI language. Default behavior is to detect from the visitor's browser. Can be overridden per shortcode via language=&quot;es&quot;."
PLG_CONTENT_CRUMB_FIELD_LANGUAGE_DEFAULT="— Auto-detect from browser —"

PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_LABEL="CSS Template"
PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_DESC="Full Width fits the content area. Full Width (Force Viewport) breaks out to span the full browser width."
PLG_CONTENT_CRUMB_FIELD_CSS_TEMPLATE_NONE="— None —"
Expand Down
1 change: 1 addition & 0 deletions packages/plugin/src/Extension/Crumb.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public function onContentPrepare(string $context, &$article = null, &$params = n
'service_body' => (string) $this->params->get('service_body', ''),
'format_ids' => (string) $this->params->get('format_ids', ''),
'view' => (string) $this->params->get('view', ''),
'language' => (string) $this->params->get('language', ''),
'css_template' => (string) $this->params->get('css_template', ''),
'base_path' => (string) $this->params->get('base_path', ''),
'geolocation_radius' => (string) $this->params->get('geolocation_radius', ''),
Expand Down
16 changes: 16 additions & 0 deletions packages/plugin/src/Helper/CrumbRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ final class CrumbRenderer
{
public const ALLOWED_VIEWS = ['list', 'map'];

/** Languages the widget supports (mirrors src/stores/localization.ts). */
public const SUPPORTED_LANGUAGES = ['en', 'es', 'fr', 'de', 'pt', 'it', 'sv', 'da', 'el', 'fa', 'pl', 'ru', 'ja'];

/**
* Build the widget markup. Returns an empty string when no server is
* configured, plus a visible error notice for editors.
Expand Down Expand Up @@ -109,6 +112,19 @@ public static function render(array $settings, array $overrides = []): string
}
}

// Language: per-shortcode override wins; else the saved plugin/module param
// fills in unless widget_config JSON already set one. Unsupported codes
// are silently dropped (widget then auto-detects from navigator.language).
$langOverride = isset($overrides['language']) ? strtolower(trim((string) $overrides['language'])) : '';
if ($langOverride !== '' && \in_array($langOverride, self::SUPPORTED_LANGUAGES, true)) {
$configArray['language'] = $langOverride;
} elseif (!isset($configArray['language'])) {
$langSetting = strtolower(trim((string) ($settings['language'] ?? '')));
if ($langSetting !== '' && \in_array($langSetting, self::SUPPORTED_LANGUAGES, true)) {
$configArray['language'] = $langSetting;
}
}

$configScript = '';
if (!empty($configArray)) {
$configScript = '<script>window.CrumbWidgetConfig = '
Expand Down
2 changes: 1 addition & 1 deletion pkg_crumb.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<authorEmail>bmlt-enabled@googlegroups.com</authorEmail>
<authorUrl>https://github.com/bmlt-enabled/crumb-joomla</authorUrl>
<packagename>crumb</packagename>
<version>0.5.0</version>
<version>0.6.0</version>
<url>https://github.com/bmlt-enabled/crumb-joomla</url>
<packager>bmlt-enabled</packager>
<packagerurl>https://github.com/bmlt-enabled</packagerurl>
Expand Down
58 changes: 58 additions & 0 deletions tests/Unit/CrumbRendererTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,64 @@ public function testEmptyGeolocationRadiusProducesNoConfigScript(): void
$this->assertStringNotContainsString('CrumbWidgetConfig', $output);
}

public function testLanguageSettingMergesIntoConfig(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'language' => 'es',
]);
$this->assertStringContainsString('window.CrumbWidgetConfig', $output);
$this->assertStringContainsString('"language":"es"', $output);
}

public function testLanguageOverrideTakesPrecedenceOverSetting(): void
{
$output = CrumbRenderer::render(
['server' => 'https://x/main_server', 'language' => 'es'],
['language' => 'de']
);
$this->assertStringContainsString('"language":"de"', $output);
$this->assertStringNotContainsString('"language":"es"', $output);
}

public function testLanguageOverrideDroppedForUnsupportedCode(): void
{
$output = CrumbRenderer::render(
['server' => 'https://x/main_server', 'language' => 'es'],
['language' => 'banana']
);
// Saved 'es' fills in because the unsupported override never wrote a value.
$this->assertStringContainsString('"language":"es"', $output);
}

public function testWidgetConfigLanguageTakesPrecedenceOverSetting(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'language' => 'es',
'widget_config' => '{"language":"fr"}',
]);
$this->assertStringContainsString('"language":"fr"', $output);
}

public function testEmptyLanguageProducesNoConfigScript(): void
{
$output = CrumbRenderer::render([
'server' => 'https://x/main_server',
'language' => '',
]);
$this->assertStringNotContainsString('CrumbWidgetConfig', $output);
}

public function testLanguageOverrideTrimsAndLowercases(): void
{
$output = CrumbRenderer::render(
['server' => 'https://x/main_server'],
['language' => ' FR ']
);
$this->assertStringContainsString('"language":"fr"', $output);
}

public function testUpdateUrlSettingEmitsDataAttribute(): void
{
$output = CrumbRenderer::render([
Expand Down
Loading