Skip to content

Commit 4200ec5

Browse files
authored
feat: move lookup to MessageCollection and bring parity with FormatJS (#12)
1 parent ad76f32 commit 4200ec5

11 files changed

Lines changed: 367 additions & 229 deletions

src/Config.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/**
4+
* This file is part of skillshare/formatphp
5+
*
6+
* skillshare/formatphp is open source software: you can distribute
7+
* it and/or modify it under the terms of the MIT License
8+
* (the "License"). You may not use this file except in
9+
* compliance with the License.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
* implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*
17+
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
18+
* @license https://opensource.org/licenses/MIT MIT License
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace FormatPHP;
24+
25+
use FormatPHP\Extractor\IdInterpolator;
26+
use FormatPHP\Intl\LocaleInterface;
27+
28+
/**
29+
* FormatPHP configuration
30+
*/
31+
class Config implements ConfigInterface
32+
{
33+
private LocaleInterface $locale;
34+
private ?LocaleInterface $defaultLocale;
35+
private string $idInterpolatorPattern;
36+
37+
public function __construct(
38+
LocaleInterface $locale,
39+
?LocaleInterface $defaultLocale = null,
40+
string $idInterpolatorPattern = IdInterpolator::DEFAULT_ID_INTERPOLATION_PATTERN
41+
) {
42+
$this->locale = $locale;
43+
$this->defaultLocale = $defaultLocale;
44+
$this->idInterpolatorPattern = $idInterpolatorPattern;
45+
}
46+
47+
public function getDefaultLocale(): ?LocaleInterface
48+
{
49+
return $this->defaultLocale;
50+
}
51+
52+
public function getIdInterpolatorPattern(): string
53+
{
54+
return $this->idInterpolatorPattern;
55+
}
56+
57+
public function getLocale(): LocaleInterface
58+
{
59+
return $this->locale;
60+
}
61+
}

src/ConfigInterface.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,4 @@ public function getIdInterpolatorPattern(): string;
7272
* Returns locale to use for translation and localization
7373
*/
7474
public function getLocale(): LocaleInterface;
75-
76-
/**
77-
* Returns a collection of translation messages
78-
*/
79-
public function getMessages(): MessageCollection;
8075
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* This file is part of skillshare/formatphp
5+
*
6+
* skillshare/formatphp is open source software: you can distribute
7+
* it and/or modify it under the terms of the MIT License
8+
* (the "License"). You may not use this file except in
9+
* compliance with the License.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
* implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*
17+
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
18+
* @license https://opensource.org/licenses/MIT MIT License
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace FormatPHP\Exception;
24+
25+
use RuntimeException;
26+
27+
/**
28+
* Thrown when we are unable to format a message
29+
*/
30+
class UnableToFormatMessageException extends RuntimeException implements FormatPHPExceptionInterface
31+
{
32+
}

src/FormatPHP.php

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -22,63 +22,43 @@
2222

2323
namespace FormatPHP;
2424

25-
use FormatPHP\Extractor\IdInterpolator;
26-
use FormatPHP\Intl\LocaleInterface;
25+
use FormatPHP\Intl\MessageFormat;
2726

2827
/**
2928
* FormatPHP internationalization and localization
3029
*/
31-
class FormatPHP implements ConfigInterface, FormatterInterface
30+
class FormatPHP implements FormatterInterface
3231
{
33-
private LocaleInterface $locale;
32+
private ConfigInterface $config;
3433
private MessageCollection $messages;
35-
private ?LocaleInterface $defaultLocale;
34+
private MessageFormat $messageFormat;
3635

36+
/**
37+
* @throws Exception\InvalidArgumentException
38+
*/
3739
public function __construct(
38-
LocaleInterface $locale,
39-
MessageCollection $messages,
40-
?LocaleInterface $defaultLocale = null
40+
ConfigInterface $config,
41+
MessageCollection $messages
4142
) {
42-
$this->locale = $locale;
43+
$this->config = $config;
4344
$this->messages = $messages;
44-
$this->defaultLocale = $defaultLocale;
45-
}
46-
47-
public function getDefaultLocale(): ?LocaleInterface
48-
{
49-
return $this->defaultLocale;
50-
}
51-
52-
public function getLocale(): LocaleInterface
53-
{
54-
return $this->locale;
55-
}
56-
57-
public function getMessages(): MessageCollection
58-
{
59-
return $this->messages;
60-
}
61-
62-
public function getIdInterpolatorPattern(): string
63-
{
64-
return IdInterpolator::DEFAULT_ID_INTERPOLATION_PATTERN;
45+
$this->messageFormat = new MessageFormat($config->getLocale());
6546
}
6647

6748
/**
6849
* @throws Exception\InvalidArgumentException
50+
* @throws Exception\UnableToFormatMessageException
6951
*
7052
* @inheritdoc
7153
*/
7254
public function formatMessage(array $descriptor, array $values = []): string
7355
{
74-
$descriptorInstance = new Descriptor(
56+
$messagePattern = $this->messages->getMessageByDescriptor(new Descriptor(
7557
$descriptor['id'] ?? null,
7658
$descriptor['defaultMessage'] ?? null,
7759
$descriptor['description'] ?? null,
78-
);
79-
80-
$formatter = new Intl\MessageFormat($this);
60+
));
8161

82-
return $formatter->format($descriptorInstance, $values);
62+
return $this->messageFormat->format($messagePattern, $values);
8363
}
8464
}

src/Intl/MessageFormat.php

Lines changed: 22 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -22,111 +22,48 @@
2222

2323
namespace FormatPHP\Intl;
2424

25-
use FormatPHP\ConfigInterface;
26-
use FormatPHP\DescriptorInterface;
2725
use FormatPHP\Exception\InvalidArgumentException;
28-
use FormatPHP\Exception\MessageNotFoundException;
29-
use FormatPHP\Exception\UnableToGenerateMessageIdException;
30-
use FormatPHP\Extractor\IdInterpolator;
26+
use FormatPHP\Exception\UnableToFormatMessageException;
27+
use IntlException as PhpIntlException;
28+
use Locale as PhpLocale;
3129
use MessageFormatter as PhpMessageFormatter;
3230

33-
use function preg_replace;
3431
use function sprintf;
35-
use function trim;
3632

3733
/**
38-
* Formats a message using {@link https://unicode-org.github.io/icu/userguide/format_parse/messages/ ICU Message syntax}
34+
* Formats an ICU message format pattern
3935
*/
40-
class MessageFormat
36+
class MessageFormat implements MessageFormatInterface
4137
{
42-
private ConfigInterface $config;
43-
44-
public function __construct(ConfigInterface $config)
45-
{
46-
$this->config = $config;
47-
}
38+
private LocaleInterface $locale;
4839

4940
/**
50-
* Returns a translated string for the given descriptor ID
51-
*
52-
* If the descriptor does not have an ID, we will use a combination of the
53-
* defaultMessage and description to create an ID.
54-
*
55-
* If we cannot find the given ID in the configured messages, we will use
56-
* the descriptor's defaultMessage, if provided.
57-
*
58-
* @param array<array-key, int | float | string> $values
59-
*
6041
* @throws InvalidArgumentException
6142
*/
62-
public function format(DescriptorInterface $descriptor, array $values = []): string
43+
public function __construct(?LocaleInterface $locale = null)
6344
{
64-
return (string) PhpMessageFormatter::formatMessage(
65-
(string) $this->config->getLocale()->baseName(),
66-
$this->getMessage($descriptor),
67-
$values,
68-
);
45+
$this->locale = $locale ?? new Locale(PhpLocale::getDefault());
6946
}
7047

7148
/**
72-
* @throws InvalidArgumentException
49+
* @inheritdoc
7350
*/
74-
private function buildMessageId(DescriptorInterface $descriptor): string
51+
public function format(string $pattern, array $values = []): string
7552
{
7653
try {
77-
$messageId = (new IdInterpolator())->generateId(
78-
$descriptor,
79-
$this->config->getIdInterpolatorPattern(),
54+
$formatter = new PhpMessageFormatter((string) $this->locale->baseName(), $pattern);
55+
56+
return (string) $formatter->format($values);
57+
} catch (PhpIntlException $exception) {
58+
throw new UnableToFormatMessageException(
59+
sprintf(
60+
'Unable to format message with pattern "%s" for locale "%s"',
61+
$pattern,
62+
(string) $this->locale->baseName(),
63+
),
64+
(int) $exception->getCode(),
65+
$exception,
8066
);
81-
} catch (UnableToGenerateMessageIdException $exception) {
82-
$messageId = '';
83-
}
84-
85-
return $messageId;
86-
}
87-
88-
/**
89-
* @throws InvalidArgumentException
90-
*/
91-
private function getMessage(DescriptorInterface $descriptor): string
92-
{
93-
$messageId = $this->buildMessageId($descriptor);
94-
95-
try {
96-
return $this->lookupMessage($messageId);
97-
} catch (MessageNotFoundException $exception) {
98-
if ($descriptor->getDefaultMessage() !== null) {
99-
return trim((string) preg_replace('/\n\s*/', ' ', (string) $descriptor->getDefaultMessage()));
100-
}
101-
}
102-
103-
return $messageId;
104-
}
105-
106-
/**
107-
* @throws MessageNotFoundException
108-
*/
109-
private function lookupMessage(string $messageId): string
110-
{
111-
$config = $this->config;
112-
113-
try {
114-
return $config->getMessages()->getMessage($messageId, $config->getLocale());
115-
} catch (MessageNotFoundException $exception) {
116-
try {
117-
// Try falling back to a locale made up of just the language.
118-
return $config->getMessages()->getMessage(
119-
$messageId,
120-
new Locale((string) $config->getLocale()->language()),
121-
);
122-
} catch (MessageNotFoundException $exception) {
123-
$defaultLocale = $config->getDefaultLocale();
124-
if ($defaultLocale !== null) {
125-
return $config->getMessages()->getMessage($messageId, $defaultLocale);
126-
}
127-
}
12867
}
129-
130-
throw new MessageNotFoundException(sprintf('Unable to look up message with ID "%s".', $messageId));
13168
}
13269
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
/**
4+
* This file is part of skillshare/formatphp
5+
*
6+
* skillshare/formatphp is open source software: you can distribute
7+
* it and/or modify it under the terms of the MIT License
8+
* (the "License"). You may not use this file except in
9+
* compliance with the License.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14+
* implied. See the License for the specific language governing
15+
* permissions and limitations under the License.
16+
*
17+
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
18+
* @license https://opensource.org/licenses/MIT MIT License
19+
*/
20+
21+
declare(strict_types=1);
22+
23+
namespace FormatPHP\Intl;
24+
25+
use FormatPHP\Exception\UnableToFormatMessageException;
26+
27+
/**
28+
* A message formatter designed to fit within the style and function of
29+
* ECMA-402 formatters
30+
*
31+
* @link https://unicode-org.github.io/icu/userguide/format_parse/messages/
32+
* @link https://github.com/unicode-org/message-format-wg
33+
* @link https://www.php.net/MessageFormatter
34+
* @link https://formatjs.io/docs/intl-messageformat/
35+
* @link https://github.com/tc39/ecma402/issues/92
36+
* @link http://messageformat.github.io/messageformat/api/core.messageformat/
37+
*/
38+
interface MessageFormatInterface
39+
{
40+
/**
41+
* Formats an ICU message format pattern, using a locale configured with the
42+
* message format instance and replacing any placeholders with the provided
43+
* values
44+
*
45+
* @param array<array-key, float | int | string> $values
46+
*
47+
* @throws UnableToFormatMessageException
48+
*/
49+
public function format(string $pattern, array $values = []): string;
50+
}

0 commit comments

Comments
 (0)