Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like OC_L10N often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use OC_L10N, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 42 | class OC_L10N implements \OCP\IL10N { |
||
| 43 | /** |
||
| 44 | * cache |
||
| 45 | */ |
||
| 46 | protected static $cache = array(); |
||
| 47 | protected static $availableLanguages = array(); |
||
| 48 | |||
| 49 | /** |
||
| 50 | * The best language |
||
| 51 | */ |
||
| 52 | protected static $language = ''; |
||
| 53 | |||
| 54 | /** |
||
| 55 | * App of this object |
||
| 56 | */ |
||
| 57 | protected $app; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * Language of this object |
||
| 61 | */ |
||
| 62 | protected $lang; |
||
| 63 | |||
| 64 | /** |
||
| 65 | * Translations |
||
| 66 | */ |
||
| 67 | private $translations = array(); |
||
| 68 | |||
| 69 | /** |
||
| 70 | * Plural forms (string) |
||
| 71 | */ |
||
| 72 | private $pluralFormString = 'nplurals=2; plural=(n != 1);'; |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Plural forms (function) |
||
| 76 | */ |
||
| 77 | private $pluralFormFunction = null; |
||
| 78 | |||
| 79 | /** |
||
| 80 | * get an L10N instance |
||
| 81 | * @param string $app |
||
| 82 | * @param string|null $lang |
||
| 83 | * @return \OCP\IL10N |
||
| 84 | * @deprecated Use \OC::$server->getL10NFactory()->get() instead |
||
| 85 | */ |
||
| 86 | 1 | public static function get($app, $lang=null) { |
|
| 87 | 1 | return \OC::$server->getL10NFactory()->get($app, $lang); |
|
| 88 | } |
||
| 89 | |||
| 90 | /** |
||
| 91 | * The constructor |
||
| 92 | * @param string $app app requesting l10n |
||
| 93 | * @param string $lang default: null Language |
||
| 94 | * |
||
| 95 | * If language is not set, the constructor tries to find the right |
||
| 96 | * language. |
||
| 97 | */ |
||
| 98 | 121 | public function __construct($app, $lang = null) { |
|
| 99 | 121 | $this->app = $app; |
|
| 100 | 121 | $this->lang = $lang; |
|
| 101 | 121 | } |
|
| 102 | |||
| 103 | /** |
||
| 104 | * @return string |
||
| 105 | */ |
||
| 106 | 6 | public static function setLanguageFromRequest() { |
|
| 107 | 6 | if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { |
|
| 108 | 5 | $available = self::findAvailableLanguages(); |
|
| 109 | |||
| 110 | // E.g. make sure that 'de' is before 'de_DE'. |
||
| 111 | 5 | sort($available); |
|
| 112 | |||
| 113 | 5 | $preferences = preg_split('/,\s*/', strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE'])); |
|
| 114 | 5 | foreach ($preferences as $preference) { |
|
| 115 | 5 | list($preferred_language) = explode(';', $preference); |
|
| 116 | 5 | $preferred_language = str_replace('-', '_', $preferred_language); |
|
| 117 | 5 | View Code Duplication | foreach ($available as $available_language) { |
|
|
|||
| 118 | 5 | if ($preferred_language === strtolower($available_language)) { |
|
| 119 | 2 | if (!self::$language) { |
|
| 120 | 2 | self::$language = $available_language; |
|
| 121 | 2 | } |
|
| 122 | 2 | return $available_language; |
|
| 123 | } |
||
| 124 | 5 | } |
|
| 125 | 3 | View Code Duplication | foreach ($available as $available_language) { |
| 126 | 3 | if (substr($preferred_language, 0, 2) === $available_language) { |
|
| 127 | 2 | if (!self::$language) { |
|
| 128 | 2 | self::$language = $available_language; |
|
| 129 | 2 | } |
|
| 130 | 2 | return $available_language; |
|
| 131 | } |
||
| 132 | 3 | } |
|
| 133 | 1 | } |
|
| 134 | 1 | } |
|
| 135 | |||
| 136 | 2 | self::$language = 'en'; |
|
| 137 | // Last try: English |
||
| 138 | 2 | return 'en'; |
|
| 139 | } |
||
| 140 | |||
| 141 | /** |
||
| 142 | * @param $transFile |
||
| 143 | * @param bool $mergeTranslations |
||
| 144 | * @return bool |
||
| 145 | */ |
||
| 146 | 3 | public function load($transFile, $mergeTranslations = false) { |
|
| 147 | 3 | $this->app = true; |
|
| 148 | |||
| 149 | 3 | $json = json_decode(file_get_contents($transFile), true); |
|
| 150 | 3 | if (!is_array($json)) { |
|
| 151 | return false; |
||
| 152 | } |
||
| 153 | |||
| 154 | 3 | $this->pluralFormString = $json['pluralForm']; |
|
| 155 | 3 | $translations = $json['translations']; |
|
| 156 | |||
| 157 | 3 | if ($mergeTranslations) { |
|
| 158 | $this->translations = array_merge($this->translations, $translations); |
||
| 159 | } else { |
||
| 160 | 3 | $this->translations = $translations; |
|
| 161 | } |
||
| 162 | |||
| 163 | 3 | return true; |
|
| 164 | } |
||
| 165 | |||
| 166 | 170 | protected function init() { |
|
| 208 | |||
| 209 | /** |
||
| 210 | * Creates a function that The constructor |
||
| 211 | * |
||
| 212 | * If language is not set, the constructor tries to find the right |
||
| 213 | * language. |
||
| 214 | * |
||
| 215 | * Parts of the code is copied from Habari: |
||
| 216 | * https://github.com/habari/system/blob/master/classes/locale.php |
||
| 217 | * @param string $string |
||
| 218 | * @return string |
||
| 219 | */ |
||
| 220 | 5 | protected function createPluralFormFunction($string){ |
|
| 221 | 5 | if(preg_match( '/^\s*nplurals\s*=\s*(\d+)\s*;\s*plural=(.*)$/u', $string, $matches)) { |
|
| 222 | // sanitize |
||
| 223 | 5 | $nplurals = preg_replace( '/[^0-9]/', '', $matches[1] ); |
|
| 224 | 5 | $plural = preg_replace( '#[^n0-9:\(\)\?\|\&=!<>+*/\%-]#', '', $matches[2] ); |
|
| 225 | |||
| 226 | 5 | $body = str_replace( |
|
| 227 | 5 | array( 'plural', 'n', '$n$plurals', ), |
|
| 228 | 5 | array( '$plural', '$n', '$nplurals', ), |
|
| 229 | 5 | 'nplurals='. $nplurals . '; plural=' . $plural |
|
| 230 | 5 | ); |
|
| 231 | |||
| 232 | // add parents |
||
| 233 | // important since PHP's ternary evaluates from left to right |
||
| 234 | 5 | $body .= ';'; |
|
| 235 | 5 | $res = ''; |
|
| 236 | 5 | $p = 0; |
|
| 237 | 5 | for($i = 0; $i < strlen($body); $i++) { |
|
| 238 | 5 | $ch = $body[$i]; |
|
| 239 | switch ( $ch ) { |
||
| 240 | 5 | case '?': |
|
| 241 | 2 | $res .= ' ? ('; |
|
| 242 | 2 | $p++; |
|
| 243 | 2 | break; |
|
| 244 | 5 | case ':': |
|
| 245 | 2 | $res .= ') : ('; |
|
| 246 | 2 | break; |
|
| 247 | 5 | case ';': |
|
| 248 | 5 | $res .= str_repeat( ')', $p ) . ';'; |
|
| 249 | 5 | $p = 0; |
|
| 250 | 5 | break; |
|
| 251 | 5 | default: |
|
| 252 | 5 | $res .= $ch; |
|
| 253 | 5 | } |
|
| 254 | 5 | } |
|
| 255 | |||
| 256 | 5 | $body = $res . 'return ($plural>=$nplurals?$nplurals-1:$plural);'; |
|
| 257 | 5 | return create_function('$n', $body); |
|
| 258 | } |
||
| 259 | else { |
||
| 260 | // default: one plural form for all cases but n==1 (english) |
||
| 261 | return create_function( |
||
| 262 | '$n', |
||
| 263 | '$nplurals=2;$plural=($n==1?0:1);return ($plural>=$nplurals?$nplurals-1:$plural);' |
||
| 264 | ); |
||
| 265 | } |
||
| 266 | } |
||
| 267 | |||
| 268 | /** |
||
| 269 | * Translating |
||
| 270 | * @param string $text The text we need a translation for |
||
| 271 | * @param array $parameters default:array() Parameters for sprintf |
||
| 272 | * @return \OC_L10N_String Translation or the same text |
||
| 273 | * |
||
| 274 | * Returns the translation. If no translation is found, $text will be |
||
| 275 | * returned. |
||
| 276 | */ |
||
| 277 | 143 | public function t($text, $parameters = array()) { |
|
| 278 | 143 | return new OC_L10N_String($this, $text, $parameters); |
|
| 279 | } |
||
| 280 | |||
| 281 | /** |
||
| 282 | * Translating |
||
| 283 | * @param string $text_singular the string to translate for exactly one object |
||
| 284 | * @param string $text_plural the string to translate for n objects |
||
| 285 | * @param integer $count Number of objects |
||
| 286 | * @param array $parameters default:array() Parameters for sprintf |
||
| 287 | * @return \OC_L10N_String Translation or the same text |
||
| 288 | * |
||
| 289 | * Returns the translation. If no translation is found, $text will be |
||
| 290 | * returned. %n will be replaced with the number of objects. |
||
| 291 | * |
||
| 292 | * The correct plural is determined by the plural_forms-function |
||
| 293 | * provided by the po file. |
||
| 294 | * |
||
| 295 | */ |
||
| 296 | 40 | public function n($text_singular, $text_plural, $count, $parameters = array()) { |
|
| 297 | 40 | $this->init(); |
|
| 298 | 40 | $identifier = "_${text_singular}_::_${text_plural}_"; |
|
| 299 | 40 | if( array_key_exists($identifier, $this->translations)) { |
|
| 300 | 11 | return new OC_L10N_String( $this, $identifier, $parameters, $count ); |
|
| 301 | }else{ |
||
| 302 | 29 | if($count === 1) { |
|
| 303 | 4 | return new OC_L10N_String($this, $text_singular, $parameters, $count); |
|
| 304 | }else{ |
||
| 305 | 25 | return new OC_L10N_String($this, $text_plural, $parameters, $count); |
|
| 306 | } |
||
| 307 | } |
||
| 308 | } |
||
| 309 | |||
| 310 | /** |
||
| 311 | * getTranslations |
||
| 312 | * @return array Fetch all translations |
||
| 313 | * |
||
| 314 | * Returns an associative array with all translations |
||
| 315 | */ |
||
| 316 | public function getTranslations() { |
||
| 317 | 131 | $this->init(); |
|
| 318 | 131 | return $this->translations; |
|
| 319 | } |
||
| 320 | |||
| 321 | /** |
||
| 322 | * getPluralFormFunction |
||
| 323 | * @return string the plural form function |
||
| 324 | * |
||
| 325 | * returned function accepts the argument $n |
||
| 326 | */ |
||
| 327 | public function getPluralFormFunction() { |
||
| 328 | 11 | $this->init(); |
|
| 329 | 11 | if(is_null($this->pluralFormFunction)) { |
|
| 330 | 5 | $this->pluralFormFunction = $this->createPluralFormFunction($this->pluralFormString); |
|
| 331 | 5 | } |
|
| 332 | 11 | return $this->pluralFormFunction; |
|
| 333 | } |
||
| 334 | |||
| 335 | /** |
||
| 336 | * Localization |
||
| 337 | * @param string $type Type of localization |
||
| 338 | * @param array|int|string $data parameters for this localization |
||
| 339 | * @param array $options |
||
| 340 | * @return string|false |
||
| 341 | * |
||
| 342 | * Returns the localized data. |
||
| 343 | * |
||
| 344 | * Implemented types: |
||
| 345 | * - date |
||
| 346 | * - Creates a date |
||
| 347 | * - params: timestamp (int/string) |
||
| 348 | * - datetime |
||
| 349 | * - Creates date and time |
||
| 350 | * - params: timestamp (int/string) |
||
| 351 | * - time |
||
| 352 | * - Creates a time |
||
| 353 | * - params: timestamp (int/string) |
||
| 354 | */ |
||
| 355 | public function l($type, $data, $options = array()) { |
||
| 356 | 41 | if ($type === 'firstday') { |
|
| 357 | 2 | return $this->getFirstWeekDay(); |
|
| 358 | } |
||
| 359 | 39 | if ($type === 'jsdate') { |
|
| 360 | return $this->getDateFormat(); |
||
| 361 | } |
||
| 362 | |||
| 363 | 39 | $this->init(); |
|
| 364 | 39 | $value = new DateTime(); |
|
| 365 | 39 | if($data instanceof DateTime) { |
|
| 366 | 27 | $value = $data; |
|
| 367 | 39 | } elseif(is_string($data) && !is_numeric($data)) { |
|
| 368 | $data = strtotime($data); |
||
| 369 | $value->setTimestamp($data); |
||
| 370 | } else { |
||
| 371 | 12 | $value->setTimestamp($data); |
|
| 372 | } |
||
| 373 | |||
| 374 | // Use the language of the instance, before falling back to the current user's language |
||
| 375 | 39 | $locale = $this->lang; |
|
| 376 | 39 | if ($locale === null) { |
|
| 377 | 6 | $locale = self::findLanguage(); |
|
| 378 | 6 | } |
|
| 379 | 39 | $locale = $this->transformToCLDRLocale($locale); |
|
| 380 | |||
| 381 | 39 | $options = array_merge(array('width' => 'long'), $options); |
|
| 382 | 39 | $width = $options['width']; |
|
| 383 | switch($type) { |
||
| 384 | 39 | case 'date': |
|
| 385 | 10 | return Punic\Calendar::formatDate($value, $width, $locale); |
|
| 386 | 30 | case 'datetime': |
|
| 387 | 22 | return Punic\Calendar::formatDatetime($value, $width, $locale); |
|
| 388 | 8 | case 'time': |
|
| 389 | 8 | return Punic\Calendar::formatTime($value, $width, $locale); |
|
| 390 | default: |
||
| 391 | return false; |
||
| 392 | } |
||
| 393 | } |
||
| 394 | |||
| 395 | /** |
||
| 396 | * The code (en, de, ...) of the language that is used for this OC_L10N object |
||
| 397 | * |
||
| 398 | * @return string language |
||
| 399 | */ |
||
| 400 | public function getLanguageCode() { |
||
| 401 | 5 | return $this->lang ? $this->lang : self::findLanguage(); |
|
| 402 | } |
||
| 403 | |||
| 404 | /** |
||
| 405 | * find the best language |
||
| 406 | * @param string $app |
||
| 407 | * @return string language |
||
| 408 | * |
||
| 409 | * If nothing works it returns 'en' |
||
| 410 | */ |
||
| 411 | public static function findLanguage($app = null) { |
||
| 412 | 18 | if (self::$language != '' && self::languageExists($app, self::$language)) { |
|
| 413 | 10 | return self::$language; |
|
| 414 | } |
||
| 415 | |||
| 416 | 8 | $config = \OC::$server->getConfig(); |
|
| 417 | 8 | $userId = \OC_User::getUser(); |
|
| 418 | |||
| 419 | 8 | if($userId && $config->getUserValue($userId, 'core', 'lang')) { |
|
| 420 | $lang = $config->getUserValue($userId, 'core', 'lang'); |
||
| 421 | self::$language = $lang; |
||
| 422 | if(self::languageExists($app, $lang)) { |
||
| 423 | return $lang; |
||
| 424 | } |
||
| 425 | } |
||
| 426 | |||
| 427 | 8 | $default_language = $config->getSystemValue('default_language', false); |
|
| 428 | |||
| 429 | 8 | if($default_language !== false) { |
|
| 430 | 2 | return $default_language; |
|
| 431 | } |
||
| 432 | |||
| 433 | 6 | $lang = self::setLanguageFromRequest(); |
|
| 434 | 6 | if($userId && !$config->getUserValue($userId, 'core', 'lang')) { |
|
| 435 | $config->setUserValue($userId, 'core', 'lang', $lang); |
||
| 436 | } |
||
| 437 | |||
| 438 | 6 | return $lang; |
|
| 439 | } |
||
| 440 | |||
| 441 | /** |
||
| 442 | * find the l10n directory |
||
| 443 | * @param string $app App that needs to be translated |
||
| 444 | * @return string directory |
||
| 445 | */ |
||
| 446 | protected static function findI18nDir($app) { |
||
| 447 | // find the i18n dir |
||
| 448 | 9 | $i18nDir = OC::$SERVERROOT.'/core/l10n/'; |
|
| 449 | 9 | if($app != '') { |
|
| 450 | // Check if the app is in the app folder |
||
| 451 | 8 | if(file_exists(OC_App::getAppPath($app).'/l10n/')) { |
|
| 452 | 2 | $i18nDir = OC_App::getAppPath($app).'/l10n/'; |
|
| 453 | 2 | } |
|
| 454 | else{ |
||
| 455 | 6 | $i18nDir = OC::$SERVERROOT.'/'.$app.'/l10n/'; |
|
| 456 | } |
||
| 457 | 8 | } |
|
| 458 | 9 | return $i18nDir; |
|
| 459 | } |
||
| 460 | |||
| 461 | /** |
||
| 462 | * find all available languages for an app |
||
| 463 | * @param string $app App that needs to be translated |
||
| 464 | * @return array an array of available languages |
||
| 465 | */ |
||
| 466 | public static function findAvailableLanguages($app=null) { |
||
| 502 | |||
| 503 | /** |
||
| 504 | * @param string $app |
||
| 505 | * @param string $lang |
||
| 506 | * @return bool |
||
| 507 | */ |
||
| 508 | public static function languageExists($app, $lang) { |
||
| 509 | if ($lang === 'en') {//english is always available |
||
| 510 | return true; |
||
| 511 | } |
||
| 512 | $dir = self::findI18nDir($app); |
||
| 513 | if(is_dir($dir)) { |
||
| 514 | return file_exists($dir.'/'.$lang.'.json'); |
||
| 515 | } |
||
| 518 | 2 | ||
| 519 | 2 | /** |
|
| 520 | * @return string |
||
| 521 | * @throws \Punic\Exception\ValueNotInList |
||
| 522 | */ |
||
| 523 | 41 | public function getDateFormat() { |
|
| 528 | |||
| 529 | /** |
||
| 530 | * @return int |
||
| 531 | */ |
||
| 532 | public function getFirstWeekDay() { |
||
| 537 | |||
| 538 | private function transformToCLDRLocale($locale) { |
||
| 545 | } |
||
| 546 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.