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
74 changes: 37 additions & 37 deletions assets/js/roxy-ui.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions bin/component-map.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"_meta": {
"ui_package": "@roxyapi/ui",
"ui_version_pinned": "0.21.1",
"ui_manifest_url": "https://cdn.jsdelivr.net/npm/@roxyapi/ui@0.21.1/dist/manifest.json"
"ui_version_pinned": "0.22.0",
"ui_manifest_url": "https://cdn.jsdelivr.net/npm/@roxyapi/ui@0.22.0/dist/manifest.json"
},
"operations": {
"generateNatalChart": [
Expand Down
12 changes: 10 additions & 2 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ A matching shortcode exists for every endpoint in the spec. A sample across the
* Dreams: `[roxy_search_dream_symbols q="water"]`
* Location: `[roxy_search_cities q="berlin"]`

Add lang to any shortcode to override the response language, for example `[roxy_get_crystal id="amethyst" lang="es"]`.
Readings follow your WordPress site language automatically. To pick a language yourself, open the RoxyAPI menu, Branding tab, and set the reading language. Some of the long tail shortcodes above also take a lang attribute, for example `[roxy_get_crystal id="amethyst" lang="es"]`, but the headline readings take the site or Branding setting instead.

== Installation ==

Expand Down Expand Up @@ -137,7 +137,7 @@ One key covers 12 domains. Western astrology: natal chart, daily / weekly / mont

= Can I show readings in another language? =

Yes. Open the RoxyAPI menu, Branding tab, and pick a response language: English, German, Hindi, Spanish, Turkish, Portuguese, French, or Russian. Every reading is then returned in that language. You can also override the language per shortcode with a lang attribute, for example [roxy_horoscope sign="aries" lang="es"].
Yes. Readings follow your WordPress site language on their own, so a Spanish site returns Spanish readings with nothing to configure. To run your site in one language and your readings in another, open the RoxyAPI menu, Branding tab, and pick a reading language: English, German, Hindi, Spanish, Turkish, Portuguese, French, or Russian. Every reading is then returned in that language.

= When does the plugin contact the RoxyAPI service? =

Expand Down Expand Up @@ -208,6 +208,11 @@ Yes. All RoxyAPI shortcodes work inside any page builder that supports WordPress

== Changelog ==

= 1.7.2 =
* Fixed: readings came back in English on a site set to another language. The chosen language reached the service correctly for a handful of readings and was dropped for the rest, which covered the headline ones: natal chart, kundli, panchang, synastry, Guna Milan, numerology, tarot, and biorhythm. Every reading now follows your site language, or the reading language you pick under the Branding tab.
* Updated the chart and card rendering to the latest release.
* Corrected the language instructions on this page. The site language and the Branding setting cover every reading; the lang attribute applies only to some of the long tail shortcodes.

= 1.7.1 =
* Fixed: a reading that had not been filled in yet contacted the service on every single page view and got nothing back. Because a browser asks for a site icon on each admin screen, and WordPress answers that by rendering your front page, an unconfigured reading on the front page could repeat that call on every screen you opened. An unconfigured reading now sends nothing at all.
* Fixed: readings are cached far more widely. Most of them were not cached before, so the same reading was recalculated for every visitor on every view. Repeat views of the same reading now reuse the stored result and leave your daily allowance for real traffic.
Expand Down Expand Up @@ -369,6 +374,9 @@ Yes. All RoxyAPI shortcodes work inside any page builder that supports WordPress

== Upgrade Notice ==

= 1.7.2 =
Recommended for any site that is not in English. Readings now follow your site language across every reading, including natal chart, kundli, panchang, and compatibility, which previously stayed in English.

= 1.7.1 =
Recommended for every site. Unconfigured and rejected readings no longer contact the service on every page view, and most readings are now cached, which frees up a large share of your daily allowance for real visitors.

Expand Down
2 changes: 1 addition & 1 deletion roxyapi.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
}

const ROXYAPI_VERSION = '1.7.1';
const ROXYAPI_UI_VERSION = '0.21.1';
const ROXYAPI_UI_VERSION = '0.22.0';
const ROXYAPI_PHP_MIN = '7.4.0';
const ROXYAPI_PLUGIN_FILE = __FILE__;

Expand Down
19 changes: 19 additions & 0 deletions src/Api/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,25 @@ private static function request( string $method, string $endpoint, array $payloa
'headers' => $headers,
);

/**
* `lang` is declared `in: query` on EVERY operation that accepts it, POST included, so it
* has to ride the URL even when the rest of the payload is a JSON body.
*
* It used to be left in the body on POST, where the API ignores it SILENTLY: the reading
* came back in English with a 200, so nothing surfaced the failure. That made both the
* Reading language setting and the site-locale fallback no-ops on 120 of 175 operations,
* including 13 of the 17 featured readings (natal chart, kundli, panchang, synastry,
* numerology, tarot, biorhythm). The four GET heroes translated correctly and masked it.
* Reported by a Spanish-site customer on 2026-08-05.
*/
if ( $method === 'POST' && isset( $payload['lang'] ) ) {
$lang = (string) $payload['lang'];
unset( $payload['lang'] );
if ( $lang !== '' ) {
$url = add_query_arg( 'lang', rawurlencode( $lang ), $url );
}
}

if ( $method === 'GET' && $payload ) {
$url = add_query_arg( array_map( 'rawurlencode', $payload ), $url );
} elseif ( $method === 'POST' ) {
Expand Down
158 changes: 158 additions & 0 deletions tests/phpunit/test-post-language-query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php
/**
* `lang` must reach the API on the QUERY STRING, on POST as well as GET.
*
* Every operation that accepts a language declares it `in: query` in the spec, POST included.
* The client used to leave it in the JSON body for POST, where the API ignores it SILENTLY: the
* reading came back in English with a 200, so no error, no log line, and nothing in the admin
* hinted at a failure. The blast radius was 120 of 175 operations and 13 of the 17 featured
* readings (natal chart, kundli, panchang, synastry, numerology, tarot, biorhythm), which is to
* say both the Reading language setting and the site-locale fallback did nothing on the readings
* customers actually place. The four GET heroes translated correctly and masked it. Found from a
* Spanish-site customer report on 2026-08-05.
*
* These assert on the URL and body the client actually builds, because that is the exact seam
* where the bug lived: a test that only checked "was lang injected into the payload" passed
* throughout.
*
* @package RoxyAPI
*/

namespace RoxyAPI\Tests;

use RoxyAPI\Admin\SettingsPage;
use RoxyAPI\Api\Cache;

class Test_Post_Language_Query extends \WP_UnitTestCase {

/** Last request the mock intercepted, as [url, args]. */
private array $seen = array();

public function setUp(): void {
parent::setUp();
delete_option( SettingsPage::OPTION_NAME );
Cache::flush_all();
wp_cache_flush();
$this->seen = array();
add_filter( 'pre_http_request', array( $this, 'capture' ), 10, 3 );
}

public function tearDown(): void {
remove_filter( 'pre_http_request', array( $this, 'capture' ), 10 );
delete_option( SettingsPage::OPTION_NAME );
Cache::flush_all();
wp_cache_flush();
parent::tearDown();
}

public function capture( $preempt, $args, $url ) {
$this->seen = array( $url, $args );
return array(
'headers' => array(),
'body' => wp_json_encode( array( 'ok' => true ) ),
'response' => array( 'code' => 200, 'message' => 'OK' ),
'cookies' => array(),
'filename' => null,
);
}

private function set_language( string $lang ): void {
update_option( SettingsPage::OPTION_NAME, array( 'display_language' => $lang ) );
}

/** Decoded JSON body of the captured request. */
private function body(): array {
$raw = $this->seen[1]['body'] ?? '{}';
return json_decode( (string) $raw, true ) ?: array();
}

public function test_post_sends_language_on_the_query_string_not_the_body(): void {
$this->set_language( 'es' );
\RoxyAPI\Generated\Client::generateNatalChart(
array(
'date' => '1990-05-15',
'time' => '14:30',
'latitude' => -34.6037,
'longitude' => -58.3816,
'timezone' => -3,
)
);

$this->assertStringContainsString( 'lang=es', $this->seen[0], 'POST must carry lang in the URL.' );
$this->assertArrayNotHasKey( 'lang', $this->body(), 'lang in the JSON body is ignored by the API.' );
}

public function test_post_body_still_carries_the_reading_fields(): void {
// Lifting lang out of the payload must not disturb anything else in it.
$this->set_language( 'de' );
\RoxyAPI\Generated\Client::generateNatalChart(
array(
'date' => '1990-05-15',
'time' => '14:30',
'latitude' => -34.6037,
'longitude' => -58.3816,
'timezone' => -3,
)
);

$body = $this->body();
$this->assertSame( '1990-05-15', $body['date'] ?? null );
$this->assertSame( -34.6037, $body['latitude'] ?? null );
$this->assertStringContainsString( 'lang=de', $this->seen[0] );
}

/**
* An English site resolves to an EXPLICIT `en`, not to "no language".
*
* `Language::resolve()` falls back to the `get_locale()` prefix and `en` is a supported code,
* so it returns `'en'` rather than `''`. That is pre-existing behaviour the GET path has always
* had; this asserts the POST path now matches it rather than inventing a different rule. The
* point of the test is the PLACEMENT: even the default language rides the query string and
* never the body.
*/
public function test_an_english_site_sends_lang_en_on_the_query_string(): void {
$this->set_language( '' );
add_filter( 'locale', static fn() => 'en_US' );

\RoxyAPI\Generated\Client::generateNatalChart(
array(
'date' => '1990-05-15',
'time' => '14:30',
'latitude' => -34.6037,
'longitude' => -58.3816,
'timezone' => -3,
)
);

$this->assertStringContainsString( 'lang=en', $this->seen[0] );
$this->assertArrayNotHasKey( 'lang', $this->body() );
}

public function test_an_unsupported_site_locale_sends_no_language_at_all(): void {
// Japanese is not one of the eight supported codes, so resolve() returns '' and the URL
// must stay clean rather than carrying an empty `lang=`.
$this->set_language( '' );
add_filter( 'locale', static fn() => 'ja' );

\RoxyAPI\Generated\Client::generateNatalChart(
array(
'date' => '1990-05-15',
'time' => '14:30',
'latitude' => -34.6037,
'longitude' => -58.3816,
'timezone' => -3,
)
);

$this->assertStringNotContainsString( 'lang=', $this->seen[0] );
$this->assertArrayNotHasKey( 'lang', $this->body() );
}

public function test_get_operations_keep_sending_language_in_the_query(): void {
// The GET path was always correct and must stay that way.
$this->set_language( 'fr' );
\RoxyAPI\Generated\Client::getDailyHoroscope( 'leo' );

$this->assertStringContainsString( 'lang=fr', $this->seen[0] );
}
}