Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBX-8452: Fixed string substitution for Plural Value Object when casting to string #394

Merged
merged 4 commits into from
Jul 1, 2024
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
20 changes: 0 additions & 20 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -7410,26 +7410,6 @@ parameters:
count: 1
path: src/contracts/Repository/Values/ObjectState/ObjectStateCreateStruct.php

-
message: "#^Method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Translation\\\\Message\\:\\:__construct\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#"
count: 1
path: src/contracts/Repository/Values/Translation/Message.php

-
message: "#^Property Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Translation\\\\Message\\:\\:\\$values type has no value type specified in iterable type array\\.$#"
count: 1
path: src/contracts/Repository/Values/Translation/Message.php

-
message: "#^Method Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Translation\\\\Plural\\:\\:__construct\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#"
count: 1
path: src/contracts/Repository/Values/Translation/Plural.php

-
message: "#^Property Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\Translation\\\\Plural\\:\\:\\$values type has no value type specified in iterable type array\\.$#"
count: 1
path: src/contracts/Repository/Values/Translation/Plural.php

-
message: "#^Class Ibexa\\\\Contracts\\\\Core\\\\Repository\\\\Values\\\\URL\\\\SearchResult implements generic interface IteratorAggregate but does not specify its types\\: TKey, TValue$#"
count: 1
Expand Down
18 changes: 7 additions & 11 deletions src/contracts/Repository/Values/Translation/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,30 @@ class Message extends Translation
/**
* Message string. Might use replacements like %foo%, which are replaced by
* the values specified in the values array.
*
* @var string
*/
protected $message;
protected string $message;

/**
* Translation value objects. May not contain any numbers, which might
* result in requiring plural forms. Use Plural for that.
*
* @var array
* @var array<string, scalar>
*/
protected $values;
protected array $values;

/**
* Construct singular only message from string and optional value array.
*
* @param string $message
* @param array $values
* @param array<string, scalar> $values
*/
public function __construct($message, array $values = [])
public function __construct(string $message, array $values = [])
{
$this->message = $message;
$this->values = $values;

parent::__construct();
}

/**
* {@inheritdoc}
*/
public function __toString()
{
return strtr($this->message, $this->values);
Expand Down
54 changes: 25 additions & 29 deletions src/contracts/Repository/Values/Translation/Plural.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,70 +11,66 @@
/**
* Class for translatable messages, which may contain plural forms.
*
* The message might include replacements, in the form %[A-Za-z]%. Those are
* The message might include replacements, in the form <code>%[A-Za-z]%</code>. Those are
* replaced by the values provided. A raw % can be escaped like %%.
*
* You need to provide a singular and plural variant for the string. The
* strings provided should be english and will be translated depending on the
* strings provided should be English and will be translated depending on the
* environment language.
*
* This interface follows the interfaces of XLiff, gettext, Symfony2
* Translations and Zend_Translate. For singular forms you just provide a plain
* string (with optional placeholders without effects on the plural forms). For
* potential plural forms you always provide a singular variant and an english
* simple plural variant. No implementation supports multiple different plural
* forms in one single message.
* This interface follows the interfaces of XLIFF, gettext, Symfony Translations and Zend_Translate.
* For singular forms you just provide a plain string (with optional placeholders without effects on the plural forms).
* For potential plural forms you always provide a singular variant and an English simple plural variant.
* An instance of this class can be cast to a string. In such case whether to use singular or plural form is determined
* based on the value of first element of $values array (it needs to be 1 for singular, anything else for plural).
* If plurality cannot be inferred from $values, a plural form is assumed as default. To force singular form,
* use {@see \Ibexa\Contracts\Core\Repository\Values\Translation\Message} instead.
*
* The singular / plural string could, for Symfony2, for example be converted
* to "$singular|$plural", and you would call gettext like: ngettext(
* $singular, $plural, $count ).
* No implementation supports multiple different plural forms in one single message.
*
* The singular / plural string could, for Symfony, for example be converted
* to <code>"$singular|$plural"</code>, and you would call gettext like: <code>ngettext($singular, $plural, $count ).</code>
*/
class Plural extends Translation
{
/**
* Singular string. Might use replacements like %foo%, which are replaced by
* the values specified in the values array.
*
* @var string
*/
protected $singular;
protected string $singular;

/**
* Message string. Might use replacements like %foo%, which are replaced by
* the values specified in the values array.
*
* @var string
*/
protected $plural;
protected string $plural;

/**
* Translation value objects. May not contain any numbers, which might
* result in requiring plural forms. Use MessagePlural for that.
* Translation value objects.
*
* @var array
* @var array<string, scalar>
*/
protected $values;
protected array $values;

/**
* Construct plural message from singular, plural and value array.
*
* @param string $singular
* @param string $plural
* @param array $values
* @param array<string, scalar> $values
*/
public function __construct($singular, $plural, array $values)
public function __construct(string $singular, string $plural, array $values)
{
$this->singular = $singular;
$this->plural = $plural;
$this->values = $values;

parent::__construct();
}

/**
* {@inheritdoc}
*/
public function __toString()
{
return strtr(current($this->values) == 1 ? $this->plural : $this->singular, $this->values);
$firstValue = !empty($this->values) ? current(array_values($this->values)) : null;

return strtr((int)$firstValue === 1 ? $this->singular : $this->plural, $this->values);
}
}

Expand Down
61 changes: 61 additions & 0 deletions tests/lib/Repository/Values/Translation/MessageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Tests\Core\Repository\Values\Translation;

use Ibexa\Contracts\Core\Repository\Values\Translation\Message;
use PHPUnit\Framework\TestCase;

/**
* @covers \Ibexa\Contracts\Core\Repository\Values\Translation\Message
*/
final class MessageTest extends TestCase
{
/**
* @dataProvider getDataForTestStringable
*/
public function testStringable(Message $message, string $expectedString): void
{
self::assertSame($expectedString, (string)$message);
}

/**
* @return array<string, array{\Ibexa\Contracts\Core\Repository\Values\Translation\Message, string}>
*/
public static function getDataForTestStringable(): iterable
{
yield 'message with substitution values' => [
new Message(
'Anna has some oranges in %object%',
[
'%object%' => 'a basket',
]
),
'Anna has some oranges in a basket',
];

yield 'message with multiple substitution values' => [
new Message(
'%first_name% has some data in %storage_type%',
[
'%first_name%' => 'Anna',
'%storage_type%' => 'her database',
]
),
'Anna has some data in her database',
];

yield 'message with no substitution values' => [
new Message(
'This value is not correct',
[]
),
'This value is not correct',
];
}
}
63 changes: 63 additions & 0 deletions tests/lib/Repository/Values/Translation/PluralTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

/**
* @copyright Copyright (C) Ibexa AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
declare(strict_types=1);

namespace Ibexa\Tests\Core\Repository\Values\Translation;

use Ibexa\Contracts\Core\Repository\Values\Translation\Plural;
use PHPUnit\Framework\TestCase;

/**
* @covers \Ibexa\Contracts\Core\Repository\Values\Translation\Plural
*/
final class PluralTest extends TestCase
{
/**
* @dataProvider getDataForTestStringable
*/
public function testStringable(Plural $message, string $expectedString): void
{
self::assertSame($expectedString, (string)$message);
}

/**
* @return array<string, array{\Ibexa\Contracts\Core\Repository\Values\Translation\Plural, string}>
*/
public static function getDataForTestStringable(): iterable
{
yield 'singular form' => [
new Plural(
'John has %apple_count% apple',
'John has %apple_count% apples',
[
'%apple_count%' => 1,
]
),
'John has 1 apple',
];

yield 'plural form' => [
new Plural(
'John has %apple_count% apple',
'John has %apple_count% apples',
[
'%apple_count%' => 2,
]
),
'John has 2 apples',
];

yield 'no substitution values' => [
new Plural(
'John has some apples',
'John has a lot of apples',
[]
),
'John has a lot of apples',
];
}
}
Loading