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
35 changes: 35 additions & 0 deletions src/Exception/InvalidMessageShapeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/**
* This file is part of skillshare/formatphp
*
* skillshare/formatphp is open source software: you can distribute
* it and/or modify it under the terms of the MIT License
* (the "License"). You may not use this file except in
* compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
* @license https://opensource.org/licenses/MIT MIT License
*/

declare(strict_types=1);

namespace FormatPHP\Exception;

use FormatPHP\Reader\FormatInterface;
use RuntimeException as PhpRuntimeException;

/**
* Thrown when reading a message that doesn't conform to the expected shape
*
* @see FormatInterface
*/
class InvalidMessageShapeException extends PhpRuntimeException implements FormatPHPExceptionInterface
{
}
32 changes: 32 additions & 0 deletions src/Exception/LocaleNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/**
* This file is part of skillshare/formatphp
*
* skillshare/formatphp is open source software: you can distribute
* it and/or modify it under the terms of the MIT License
* (the "License"). You may not use this file except in
* compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
* @license https://opensource.org/licenses/MIT MIT License
*/

declare(strict_types=1);

namespace FormatPHP\Exception;

use RuntimeException as PhpRuntimeException;

/**
* Thrown when unable to find a given locale messages file
*/
class LocaleNotFoundException extends PhpRuntimeException implements FormatPHPExceptionInterface
{
}
141 changes: 141 additions & 0 deletions src/MessageLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

/**
* This file is part of skillshare/formatphp
*
* skillshare/formatphp is open source software: you can distribute
* it and/or modify it under the terms of the MIT License
* (the "License"). You may not use this file except in
* compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
* @license https://opensource.org/licenses/MIT MIT License
*/

declare(strict_types=1);

namespace FormatPHP;

use FormatPHP\Exception\InvalidArgumentException;
use FormatPHP\Exception\InvalidMessageShapeException;
use FormatPHP\Exception\LocaleNotFoundException;
use FormatPHP\Exception\UnableToProcessFileException;
use FormatPHP\Intl\Locale;
use FormatPHP\Intl\LocaleInterface;
use FormatPHP\Reader\FormatInterface;
use FormatPHP\Util\FileSystemHelper;

use function array_filter;
use function array_unique;
use function array_values;
use function implode;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/**
* Loads messages for a given locale from the file system or cache
*/
final class MessageLoader
{
private Config $config;
private FileSystemHelper $fileSystemHelper;
private FormatInterface $formatReader;
private string $messagesDirectory;

/**
* @throws InvalidArgumentException
*/
public function __construct(
string $messagesDirectory,
Config $config,
FormatInterface $formatReader,
?FileSystemHelper $fileSystemHelper = null
) {
$this->config = $config;
$this->formatReader = $formatReader;
$this->fileSystemHelper = $fileSystemHelper ?? new FileSystemHelper();
$this->messagesDirectory = $this->fileSystemHelper->getRealPath($messagesDirectory);

if (!$this->fileSystemHelper->isDirectory($this->messagesDirectory)) {
throw new InvalidArgumentException(sprintf(
'Messages directory "%s" is not a valid directory',
$messagesDirectory,
));
}
}

/**
* Returns a MessageCollection according to the configuration provided to
* this MessageLoader
*
* @throws InvalidArgumentException
* @throws InvalidMessageShapeException
* @throws LocaleNotFoundException
*/
public function loadMessages(): MessageCollection
{
[$messagesData, $resolvedLocale] = $this->getLocaleMessages();

return ($this->formatReader)($this->config, $messagesData, $resolvedLocale);
}

/**
* @return array{0: array<array-key, mixed>, 1: LocaleInterface}
*
* @throws InvalidArgumentException
* @throws LocaleNotFoundException
*/
private function getLocaleMessages(): array
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function getLocaleMessages has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.

{
$messagesContents = false;
$localeResolved = null;

foreach ($this->getFallbackLocales() as $locale) {
try {
$messagesFile = $this->messagesDirectory . DIRECTORY_SEPARATOR . $locale . '.json';
$messagesContents = $this->fileSystemHelper->getJsonContents($messagesFile);
$localeResolved = new Locale($locale);

break;
} catch (UnableToProcessFileException $exception) {
continue;
}
}

if ($messagesContents === false || $localeResolved === null) {
throw new LocaleNotFoundException(sprintf(
'Unable to find a suitable locale for "%s"; please set a default locale',
$this->config->getLocale()->toString(),
));
}

return [$messagesContents, $localeResolved];
}

/**
* @return string[]
*/
private function getFallbackLocales(): array
{
$locale = $this->config->getLocale();
$defaultLocale = $this->config->getDefaultLocale();

$fallbacks = [
$locale->toString(),
$locale->baseName(),
implode('-', array_filter([$locale->language(), $locale->region()])),
$locale->language(),
$defaultLocale ? $defaultLocale->toString() : null,
];

/** @var string[] */
return array_values(array_unique(array_filter($fallbacks)));
}
}
90 changes: 90 additions & 0 deletions src/Reader/Format/FormatPHP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

/**
* This file is part of skillshare/formatphp
*
* skillshare/formatphp is open source software: you can distribute
* it and/or modify it under the terms of the MIT License
* (the "License"). You may not use this file except in
* compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
* @license https://opensource.org/licenses/MIT MIT License
*/

declare(strict_types=1);

namespace FormatPHP\Reader\Format;

use FormatPHP\Config;
use FormatPHP\Exception\InvalidMessageShapeException;
use FormatPHP\Intl\LocaleInterface;
use FormatPHP\Message;
use FormatPHP\MessageCollection;
use FormatPHP\Reader\FormatInterface;

use function assert;
use function gettype;
use function is_array;
use function is_string;
use function sprintf;

/**
* Returns a MessageCollection parsed from JSON-decoded data that was written
* using Writer\Format\FormatPHP
*
* @see \FormatPHP\Writer\Format\FormatPHP
*/
class FormatPHP implements FormatInterface
{
/**
* @inheritdoc
*/
public function __invoke(Config $config, array $data, LocaleInterface $localeResolved): MessageCollection
{
$messages = new MessageCollection($config);

foreach ($data as $messageId => $message) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

$this->validateShape($messageId, $message);
assert(is_string($messageId));
assert(isset($message['defaultMessage']));
assert(is_string($message['defaultMessage']));

$messages[] = new Message($localeResolved, $messageId, $message['defaultMessage']);
}

return $messages;
}

/**
* @param array-key $messageId
* @param mixed $message
*
* @throws InvalidMessageShapeException
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
private function validateShape($messageId, $message): void
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

{
if (!is_string($messageId)) {
throw new InvalidMessageShapeException(sprintf(
'%s expects a string message ID; received %s',
self::class,
gettype($messageId),
));
}

if (!is_array($message) || !is_string($message['defaultMessage'] ?? null)) {
throw new InvalidMessageShapeException(sprintf(
'%s expects a string defaultMessage property; defaultMessage does not exist or is not a string',
self::class,
));
}
}
}
89 changes: 89 additions & 0 deletions src/Reader/Format/Simple.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

/**
* This file is part of skillshare/formatphp
*
* skillshare/formatphp is open source software: you can distribute
* it and/or modify it under the terms of the MIT License
* (the "License"). You may not use this file except in
* compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* @copyright Copyright (c) Skillshare, Inc. <https://www.skillshare.com>
* @license https://opensource.org/licenses/MIT MIT License
*/

declare(strict_types=1);

namespace FormatPHP\Reader\Format;

use FormatPHP\Config;
use FormatPHP\Exception\InvalidMessageShapeException;
use FormatPHP\Intl\LocaleInterface;
use FormatPHP\Message;
use FormatPHP\MessageCollection;
use FormatPHP\Reader\FormatInterface;

use function assert;
use function gettype;
use function is_string;
use function sprintf;

/**
* Returns a MessageCollection parsed from JSON-decoded data that was written
* using Writer\Format\Simple
*
* @see \FormatPHP\Writer\Format\Simple
*/
class Simple implements FormatInterface
{
/**
* @inheritdoc
*/
public function __invoke(Config $config, array $data, LocaleInterface $localeResolved): MessageCollection
{
$messages = new MessageCollection($config);

foreach ($data as $messageId => $message) {
$this->validateShape($messageId, $message);
assert(is_string($messageId));
assert(is_string($message));

$messages[$messageId] = new Message($localeResolved, $messageId, $message);
}

return $messages;
}

/**
* @param array-key $messageId
* @param mixed $message
*
* @throws InvalidMessageShapeException
*
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*/
private function validateShape($messageId, $message): void
{
if (!is_string($messageId)) {
throw new InvalidMessageShapeException(sprintf(
'%s expects a string message ID; received %s',
self::class,
gettype($messageId),
));
}

if (!is_string($message)) {
throw new InvalidMessageShapeException(sprintf(
'%s expects a string message; received %s',
self::class,
gettype($message),
));
}
}
}
Loading