1<?php 2 3/** 4 * The plurals class provides support for plural forms in phpMyFAQ translations. 5 * 6 * This Source Code Form is subject to the terms of the Mozilla Public License, 7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can 8 * obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * @package phpMyFAQ 11 * @author Aurimas Fišeras <aurimas@gmail.com> 12 * @copyright 2009-2020 Aurimas Fišeras and phpMyFAQ Team 13 * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0 14 * @link https://www.phpmyfaq.de 15 * @since 2009-07-30 16 */ 17 18namespace phpMyFAQ\Language; 19 20/** 21 * Class Plurals 22 * 23 * @package phpMyFAQ\Language 24 */ 25class Plurals 26{ 27 /** 28 * The currently loaded PMF translations. 29 * 30 * @var array 31 */ 32 private $PMF_TRANSL = []; 33 34 /** 35 * The number of plural forms for current language $lang. 36 * 37 * @var int 38 */ 39 private $nPlurals; 40 41 /** 42 * The language code of current language. 43 * 44 * @var string 45 */ 46 private $lang; 47 48 /** 49 * True when there is no support for plural forms in current language $lang. 50 * 51 * @var bool 52 */ 53 private $useDefaultPluralForm; 54 55 /** 56 * Constructor. 57 * 58 * @param array $translation PMF translation array for current language 59 */ 60 public function __construct($translation) 61 { 62 $this->PMF_TRANSL = $translation; 63 $this->nPlurals = (int)$this->PMF_TRANSL['nplurals']; 64 $this->lang = $this->PMF_TRANSL['metaLanguage']; 65 66 if ($this->plural($this->lang, 0) != -1) { 67 $this->useDefaultPluralForm = false; 68 } else { 69 // @todo update $this->PMF_TRANSL with English plural messages for fall-back 70 $this->useDefaultPluralForm = true; 71 } 72 } 73 74 /** 75 * Returns the plural form for language $lang or -1 if language $lang is not supported. 76 * 77 * @link http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms 78 * @param string $lang The language code 79 * @param int $n The number used to determine the plural form 80 * @return int 81 */ 82 private function plural(string $lang, int $n): int 83 { 84 switch ($lang) { 85 // Note: expressions in .po files are not strict C expressions, so extra braces might be 86 // needed for that expression to work here (for example see 'lt') 87 case 'ar': 88 return ($n == 0) ? 0 : ($n == 1 ? 1 : ($n == 2 ? 2 : (($n % 100 >= 3 && $n % 100 <= 10) ? 3 : 89 (($n % 100 >= 11 && $n % 100 <= 99) || ($n % 100 == 1) || ($n % 100 == 2) ? 4 : 5)))); 90 case 'bn': 91 case 'he': 92 case 'hi': 93 case 'id': 94 case 'ja': 95 case 'ko': 96 case 'th': 97 case 'tr': 98 case 'tw': 99 case 'vi': 100 case 'zh': 101 return 0; 102 case 'cy': 103 return ($n == 1) ? 0 : ($n == 2 ? 1 : (($n != 8 && $n != 11) ? 2 : 3)); 104 case 'cs': 105 return ($n == 1) ? 0 : (($n >= 2 && $n <= 4) ? 1 : 2); 106 case 'da': 107 case 'de': 108 case 'el': 109 case 'en': 110 case 'es': 111 case 'eu': 112 case 'fa': 113 case 'fi': 114 case 'it': 115 case 'nb': 116 case 'nl': 117 case 'hu': 118 case 'pt': 119 case 'sv': 120 return $n != 1; 121 case 'fr': 122 case 'pt-br': 123 return $n > 1; 124 case 'lt': 125 return ($n % 10 == 1 && $n % 100 != 11) ? 0 : ($n % 10 >= 2 && ($n % 100 < 10 || $n % 100 >= 20) ? 126 1 : 2); 127 case 'lv': 128 return ($n % 10 == 1 && $n % 100 != 11) ? 0 : ($n != 0 ? 1 : 2); 129 case 'pl': 130 return ($n == 1) ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); 131 case 'ro': 132 return ($n == 1) ? 0 : (($n == 0 || ($n % 100 > 0 && $n % 100 < 20)) ? 1 : 2); 133 case 'ru': 134 case 'sr': 135 case 'uk': 136 return ($n % 10 == 1 && $n % 100 != 11) ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && 137 ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2); 138 case 'sl': 139 return ($n % 100 == 1) ? 0 : ($n % 100 == 2 ? 1 : ($n % 100 == 3 || $n % 100 == 4 ? 2 : 3)); 140 default: 141 //plural expressions can't return negative values, so use -1 to signal unsupported language 142 return -1; 143 } 144 } 145 146 /** 147 * Returns a translated string in the correct plural form, 148 * produced according to the formatting of the message. 149 * 150 * @param string $msgID Message identification 151 * @param int $n The number used to determine the plural form 152 * 153 * @return string 154 */ 155 public function getMsg($msgID, $n) 156 { 157 return sprintf($this->getMsgTemplate($msgID, $n), $n); 158 } 159 160 /** 161 * Helper function that returns the translation template in the correct plural form 162 * If translation is missing, message in English plural form is returned. 163 * 164 * @param string $msgID Message identification 165 * @param int $n The number used to determine the plural form 166 * 167 * @return string 168 */ 169 public function getMsgTemplate($msgID, $n) 170 { 171 $plural = $this->getPlural($n); 172 if (isset($this->PMF_TRANSL[$msgID][$plural])) { 173 return $this->PMF_TRANSL[$msgID][$plural]; 174 } else { 175 // translation for current plural form (>2, since we allways have 2 English plural forms) 176 // in current language is missing, so as a last resort return default English plural form 177 return $this->PMF_TRANSL[$msgID][1]; 178 } 179 } 180 181 /** 182 * Determines the correct plural form for integer $n 183 * Returned integer is from interval [0, $nPlurals). 184 * 185 * @param int $n The number used to determine the plural form 186 * 187 * @return int 188 */ 189 private function getPlural($n) 190 { 191 if ($this->useDefaultPluralForm) { 192 // this means we have to fallback to English, so return correct English plural form 193 return $this->plural('en', $n); 194 } 195 196 $plural = $this->plural($this->lang, $n); 197 if ($plural > $this->nPlurals - 1) { 198 // incorrectly defined plural function or wrong $nPlurals 199 return $this->nPlurals - 1; 200 } else { 201 return $plural; 202 } 203 } 204} 205