Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.73% covered (warning)
72.73%
16 / 22
80.00% covered (warning)
80.00%
4 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
JsonLocalizer
72.73% covered (warning)
72.73%
16 / 22
80.00% covered (warning)
80.00%
4 / 5
13.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 localizeJson
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 localizeJsonPairs
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 localizeValue
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getFormattedMessage
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Rest;
4
5use Wikimedia\Message\MessageValue;
6
7/**
8 * Utility class for json localization needs in the REST API.
9 *
10 * Much of this involves replacing custom name/value pairs (herein referred to as i18n pairs) with
11 * translated standard name/value pairs (herein referred to as schema pairs). For example, OpenAPI
12 * specifications include a "description" field represented as a name/value pair, like:
13 *   "description": "My description".
14 * We allow a "x-i18n-description" field, whose value is a MediaWiki message key, like:
15 *   "x-i18n-description": "rest-my-description-message-key"
16 * Functions in this class will replace the "x-i18n-description" with a translated "description".
17 */
18class JsonLocalizer {
19    private ResponseFactory $responseFactory;
20
21    private const LOCALIZATION_PREFIX = 'x-i18n-';
22
23    /**
24     * @param ResponseFactory $responseFactory
25     *
26     * @internal
27     */
28    public function __construct(
29        ResponseFactory $responseFactory
30    ) {
31        $this->responseFactory = $responseFactory;
32    }
33
34    /**
35     * Recursively localizes name/value pairs, where the name begins with "x-i18-n-"
36     *
37     * @param array $json the input json, as a PHP array
38     *
39     * @return array the adjusted json, or the unchanged json if no adjustments were made
40     */
41    public function localizeJson( array $json ): array {
42        return $this->localizeJsonPairs( $json, self::LOCALIZATION_PREFIX );
43    }
44
45    /**
46     * Recursively localizes name/value pairs. Pairs to be localized are identified by prefix,
47     * and values must be message keys.
48     *
49     * The resulting key has the prefix removed, and a localized value. For example this pair:
50     *   'x-i18n-description' => 'mw-rest-my-description-message-key'
51     * Would be localized to something like:
52     *   'description' => 'My Description'
53     *
54     * @param array $json the input json, as a PHP array
55     * @param string $i18nPrefix keys beginning with this prefix will be localized
56     *
57     * @return array the adjusted json, or the unchanged json if no adjustments were made
58     */
59    private function localizeJsonPairs( array $json, string $i18nPrefix ): array {
60        // Use a reference for $value, because it is potentially modified within the loop.
61        foreach ( $json as $key => &$value ) {
62            if ( is_array( $value ) ) {
63                $value = $this->localizeJsonPairs( $value, $i18nPrefix );
64            } elseif ( str_starts_with( $key, $i18nPrefix ) ) {
65                $newKey = substr( $key, strlen( $i18nPrefix ) );
66
67                // Add the description to the top of the json, for visibility in raw specs
68                $msg = new MessageValue( $value );
69                $pair = [ $newKey => $msg ];
70                $json = $pair + $json;
71                $json[$newKey] = $this->getFormattedMessage( $msg );
72
73                unset( $json[$key] );
74            }
75        }
76        return $json;
77    }
78
79    /**
80     * Returns the localized value if possible, or the non-localized value if one is
81     * available, or null otherwise. Translates only top-level keys. Useful when the value
82     * corresponding to the input key may be either a string to be used as-is or a MessageValue
83     * object to be localized.
84     *
85     * @param array $json the input json, as a PHP array
86     * @param string $key key name of the field
87     *
88     * @return ?string
89     */
90    public function localizeValue( array $json, string $key ): ?string {
91        $value = null;
92
93        if ( array_key_exists( $key, $json ) ) {
94            if ( $json[ $key ] instanceof MessageValue ) {
95                $value = $this->getFormattedMessage( $json[ $key ] );
96            } else {
97                $value = $json[ $key ];
98            }
99        }
100
101        return $value;
102    }
103
104    /**
105     * Tries to return one formatted string for a message key or message value object.
106     *
107     * @param string|MessageValue $message the message format, or a key representing it
108     *
109     * @return string
110     */
111    public function getFormattedMessage( $message ): string {
112        if ( !$message instanceof MessageValue ) {
113            $message = new MessageValue( $message );
114        }
115
116        // TODO: consider if we want to request a specific preferred language
117        return $this->responseFactory->getFormattedMessage( $message );
118    }
119
120}