diff --git a/resources/sql/autopatches/20160601.user.02.copyprefs.php b/resources/sql/autopatches/20160601.user.02.copyprefs.php new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20160601.user.02.copyprefs.php @@ -0,0 +1,59 @@ +establishConnection('w'); +$table_name = $table->getTableName(); +$prefs_table = new PhabricatorUserPreferences(); + +foreach (new LiskRawMigrationIterator($conn_w, $table_name) as $row) { + $phid = $row['phid']; + + $pref_row = queryfx_one( + $conn_w, + 'SELECT preferences FROM %T WHERE userPHID = %s', + $prefs_table->getTableName(), + $phid); + + if ($pref_row) { + try { + $prefs = phutil_json_decode($pref_row['preferences']); + } catch (Exception $ex) { + $prefs = array(); + } + } else { + $prefs = array(); + } + + $zone = $row['timezoneIdentifier']; + if (strlen($zone)) { + $prefs[PhabricatorTimezoneSetting::SETTINGKEY] = $zone; + } + + $pronoun = $row['sex']; + if (strlen($pronoun)) { + $prefs[PhabricatorPronounSetting::SETTINGKEY] = $pronoun; + } + + $translation = $row['translation']; + if (strlen($translation)) { + $prefs[PhabricatorTranslationSetting::SETTINGKEY] = $translation; + } + + if ($prefs) { + queryfx( + $conn_w, + 'INSERT INTO %T (phid, userPHID, preferences, dateModified, dateCreated) + VALUES (%s, %s, %s, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()) + ON DUPLICATE KEY UPDATE preferences = VALUES(preferences)', + $prefs_table->getTableName(), + $prefs_table->generatePHID(), + $phid, + phutil_json_encode($prefs)); + } +} + +$prefs_key = PhabricatorUserPreferencesCacheType::KEY_PREFERENCES; +PhabricatorUserCache::clearCacheForAllUsers($prefs_key); diff --git a/resources/sql/autopatches/20160601.user.03.removetime.sql b/resources/sql/autopatches/20160601.user.03.removetime.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20160601.user.03.removetime.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_user.user + DROP COLUMN timezoneIdentifier; diff --git a/resources/sql/autopatches/20160601.user.04.removetranslation.sql b/resources/sql/autopatches/20160601.user.04.removetranslation.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20160601.user.04.removetranslation.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_user.user + DROP COLUMN translation; diff --git a/resources/sql/autopatches/20160601.user.05.removesex.sql b/resources/sql/autopatches/20160601.user.05.removesex.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20160601.user.05.removesex.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_user.user + DROP COLUMN sex; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2837,6 +2837,7 @@ 'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php', 'PhabricatorOneTimeTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorOneTimeTriggerClock.php', 'PhabricatorOpcodeCacheSpec' => 'applications/cache/spec/PhabricatorOpcodeCacheSpec.php', + 'PhabricatorOptionGroupSetting' => 'applications/settings/setting/PhabricatorOptionGroupSetting.php', 'PhabricatorOwnerPathQuery' => 'applications/owners/query/PhabricatorOwnerPathQuery.php', 'PhabricatorOwnersApplication' => 'applications/owners/application/PhabricatorOwnersApplication.php', 'PhabricatorOwnersArchiveController' => 'applications/owners/controller/PhabricatorOwnersArchiveController.php', @@ -3168,6 +3169,7 @@ 'PhabricatorProjectsSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineAttachment.php', 'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php', + 'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php', 'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php', 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', @@ -3535,6 +3537,7 @@ 'PhabricatorTimeFormatSetting' => 'applications/settings/setting/PhabricatorTimeFormatSetting.php', 'PhabricatorTimeGuard' => 'infrastructure/time/PhabricatorTimeGuard.php', 'PhabricatorTimeTestCase' => 'infrastructure/time/__tests__/PhabricatorTimeTestCase.php', + 'PhabricatorTimezoneSetting' => 'applications/settings/setting/PhabricatorTimezoneSetting.php', 'PhabricatorTimezoneSetupCheck' => 'applications/config/check/PhabricatorTimezoneSetupCheck.php', 'PhabricatorTitleGlyphsSetting' => 'applications/settings/setting/PhabricatorTitleGlyphsSetting.php', 'PhabricatorToken' => 'applications/tokens/storage/PhabricatorToken.php', @@ -3565,6 +3568,7 @@ 'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php', 'PhabricatorTransactionsFulltextEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsFulltextEngineExtension.php', 'PhabricatorTransformedFile' => 'applications/files/storage/PhabricatorTransformedFile.php', + 'PhabricatorTranslationSetting' => 'applications/settings/setting/PhabricatorTranslationSetting.php', 'PhabricatorTranslationsConfigOptions' => 'applications/config/option/PhabricatorTranslationsConfigOptions.php', 'PhabricatorTriggerAction' => 'infrastructure/daemon/workers/action/PhabricatorTriggerAction.php', 'PhabricatorTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorTriggerClock.php', @@ -7455,6 +7459,7 @@ 'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting', 'PhabricatorOneTimeTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorOpcodeCacheSpec' => 'PhabricatorCacheSpec', + 'PhabricatorOptionGroupSetting' => 'PhabricatorSetting', 'PhabricatorOwnerPathQuery' => 'Phobject', 'PhabricatorOwnersApplication' => 'PhabricatorApplication', 'PhabricatorOwnersArchiveController' => 'PhabricatorOwnersController', @@ -7858,6 +7863,7 @@ 'PhabricatorProjectsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', + 'PhabricatorPronounSetting' => 'PhabricatorSelectSetting', 'PhabricatorProtocolAdapter' => 'Phobject', 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorQuery' => 'Phobject', @@ -8294,6 +8300,7 @@ 'PhabricatorTimeFormatSetting' => 'PhabricatorSelectSetting', 'PhabricatorTimeGuard' => 'Phobject', 'PhabricatorTimeTestCase' => 'PhabricatorTestCase', + 'PhabricatorTimezoneSetting' => 'PhabricatorOptionGroupSetting', 'PhabricatorTimezoneSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorTitleGlyphsSetting' => 'PhabricatorSelectSetting', 'PhabricatorToken' => array( @@ -8329,6 +8336,7 @@ 'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorTransactionsFulltextEngineExtension' => 'PhabricatorFulltextEngineExtension', 'PhabricatorTransformedFile' => 'PhabricatorFileDAO', + 'PhabricatorTranslationSetting' => 'PhabricatorOptionGroupSetting', 'PhabricatorTranslationsConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorTriggerAction' => 'Phobject', 'PhabricatorTriggerClock' => 'Phobject', diff --git a/src/applications/calendar/__tests__/CalendarTimeUtilTestCase.php b/src/applications/calendar/__tests__/CalendarTimeUtilTestCase.php --- a/src/applications/calendar/__tests__/CalendarTimeUtilTestCase.php +++ b/src/applications/calendar/__tests__/CalendarTimeUtilTestCase.php @@ -4,7 +4,7 @@ public function testTimestampsAtMidnight() { $u = new PhabricatorUser(); - $u->setTimezoneIdentifier('America/Los_Angeles'); + $u->overrideTimezoneIdentifier('America/Los_Angeles'); $days = $this->getAllDays(); foreach ($days as $day) { $data = CalendarTimeUtil::getCalendarWidgetTimestamps( @@ -19,7 +19,7 @@ public function testTimestampsStartDay() { $u = new PhabricatorUser(); - $u->setTimezoneIdentifier('America/Los_Angeles'); + $u->overrideTimezoneIdentifier('America/Los_Angeles'); $days = $this->getAllDays(); foreach ($days as $day) { $data = CalendarTimeUtil::getTimestamps( diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php --- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php +++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php @@ -164,33 +164,6 @@ return $this->getParam('herald-force-recipients', array()); } - public function getTranslation(array $objects) { - $default_translation = PhabricatorEnv::getEnvConfig('translation.provider'); - $return = null; - $recipients = array_merge( - idx($this->parameters, 'to', array()), - idx($this->parameters, 'cc', array())); - foreach (array_select_keys($objects, $recipients) as $object) { - $translation = null; - if ($object instanceof PhabricatorUser) { - $translation = $object->getTranslation(); - } - if (!$translation) { - $translation = $default_translation; - } - if ($return && $translation != $return) { - return $default_translation; - } - $return = $translation; - } - - if (!$return) { - $return = $default_translation; - } - - return $return; - } - public function addPHIDHeaders($name, array $phids) { $phids = array_unique($phids); foreach ($phids as $phid) { diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -5,6 +5,7 @@ * @task image-cache Profile Image Cache * @task factors Multi-Factor Authentication * @task handles Managing Handles + * @task settings Settings * @task cache User Cache */ final class PhabricatorUser @@ -26,15 +27,12 @@ protected $userName; protected $realName; - protected $sex; - protected $translation; protected $passwordSalt; protected $passwordHash; protected $profileImagePHID; protected $profileImageCache; protected $availabilityCache; protected $availabilityCacheTTL; - protected $timezoneIdentifier = ''; protected $consoleEnabled = 0; protected $consoleVisible = 0; @@ -68,14 +66,10 @@ private $authorities = array(); private $handlePool; private $csrfSalt; + private $timezoneOverride; protected function readField($field) { switch ($field) { - case 'timezoneIdentifier': - // If the user hasn't set one, guess the server's time. - return nonempty( - $this->timezoneIdentifier, - date_default_timezone_get()); // Make sure these return booleans. case 'isAdmin': return (bool)$this->isAdmin; @@ -191,8 +185,6 @@ self::CONFIG_COLUMN_SCHEMA => array( 'userName' => 'sort64', 'realName' => 'text128', - 'sex' => 'text4?', - 'translation' => 'text64?', 'passwordSalt' => 'text32?', 'passwordHash' => 'text128?', 'profileImagePHID' => 'phid?', @@ -204,7 +196,6 @@ 'isMailingList' => 'bool', 'isDisabled' => 'bool', 'isAdmin' => 'bool', - 'timezoneIdentifier' => 'text255', 'isEmailVerified' => 'uint32', 'isApproved' => 'uint32', 'accountSecret' => 'bytes64', @@ -261,11 +252,6 @@ return $this; } - // To satisfy PhutilPerson. - public function getSex() { - return $this->sex; - } - public function getMonogram() { return '@'.$this->getUsername(); } @@ -490,6 +476,10 @@ '(isPrimary = 1)'); } + +/* -( Settings )----------------------------------------------------------- */ + + public function getUserSetting($key) { $settings_key = PhabricatorUserPreferencesCacheType::KEY_PREFERENCES; $settings = $this->requireCacheData($settings_key); @@ -506,11 +496,51 @@ return null; } + + /** + * Test if a given setting is set to a particular value. + * + * @param const Setting key. + * @param wild Value to compare. + * @return bool True if the setting has the specified value. + * @task settings + */ public function compareUserSetting($key, $value) { $actual = $this->getUserSetting($key); return ($actual == $value); } + public function getTranslation() { + return $this->getUserSetting(PhabricatorTranslationSetting::SETTINGKEY); + } + + public function getTimezoneIdentifier() { + if ($this->timezoneOverride) { + return $this->timezoneOverride; + } + + return $this->getUserSetting(PhabricatorTimezoneSetting::SETTINGKEY); + } + + + /** + * Override the user's timezone identifier. + * + * This is primarily useful for unit tests. + * + * @param string New timezone identifier. + * @return this + * @task settings + */ + public function overrideTimezoneIdentifier($identifier) { + $this->timezoneOverride = $identifier; + return $this; + } + + public function getSex() { + return $this->getUserSetting(PhabricatorPronounSetting::SETTINGKEY); + } + public function loadPreferences() { if ($this->preferences) { return $this->preferences; @@ -1539,4 +1569,14 @@ return $usable_value; } + + /** + * @task cache + */ + public function clearCacheData($key) { + unset($this->rawCacheData[$key]); + unset($this->usableCacheData[$key]); + return $this; + } + } diff --git a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php --- a/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php +++ b/src/applications/settings/controller/PhabricatorSettingsTimezoneController.php @@ -31,6 +31,7 @@ $timezone = $request->getStr('timezone'); $pref_ignore = PhabricatorUserPreferences::PREFERENCE_IGNORE_OFFSET; + $pref_timezone = PhabricatorTimezoneSetting::SETTINGKEY; $preferences = $viewer->loadPreferences(); @@ -52,11 +53,11 @@ if (isset($options[$timezone])) { $preferences ->setPreference($pref_ignore, null) + ->setPreference($pref_timezone, $timezone) ->save(); - $viewer - ->setTimezoneIdentifier($timezone) - ->save(); + $viewer->clearCacheData( + PhabricatorUserPreferencesCacheType::KEY_PREFERENCES); } } @@ -115,9 +116,9 @@ $offset = $offset / 60; if ($offset >= 0) { - return pht('GMT-%d', $offset); + return pht('UTC-%d', $offset); } else { - return pht('GMT+%d', -$offset); + return pht('UTC+%d', -$offset); } } diff --git a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php --- a/src/applications/settings/editor/PhabricatorSettingsEditEngine.php +++ b/src/applications/settings/editor/PhabricatorSettingsEditEngine.php @@ -78,6 +78,12 @@ $viewer = $this->getViewer(); $settings = PhabricatorSetting::getAllEnabledSettings($viewer); + foreach ($settings as $key => $setting) { + $setting = clone $setting; + $setting->setViewer($viewer); + $settings[$key] = $setting; + } + $fields = array(); foreach ($settings as $setting) { foreach ($setting->newCustomEditFields($object) as $field) { diff --git a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php --- a/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorAccountSettingsPanel.php @@ -23,21 +23,29 @@ $user = $this->getUser(); $username = $user->getUsername(); + $preferences = $user->loadPreferences(); + $errors = array(); if ($request->isFormPost()) { $sex = $request->getStr('sex'); $sexes = array(PhutilPerson::SEX_MALE, PhutilPerson::SEX_FEMALE); if (in_array($sex, $sexes)) { - $user->setSex($sex); + $new_value = $sex; } else { - $user->setSex(null); + $new_value = null; } - // Checked in runtime. - $user->setTranslation($request->getStr('translation')); + $preferences->setPreference( + PhabricatorPronounSetting::SETTINGKEY, + $new_value); + + $preferences->setPreference( + PhabricatorTranslationSetting::SETTINGKEY, + $request->getStr('translation')); if (!$errors) { - $user->save(); + $preferences->save(); + return id(new AphrontRedirectResponse()) ->setURI($this->getPanelURI('?saved=true')); } diff --git a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php --- a/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorDateTimeSettingsPanel.php @@ -18,6 +18,7 @@ $user = $request->getUser(); $username = $user->getUsername(); + $pref_timezone = PhabricatorTimezoneSetting::SETTINGKEY; $pref_time = PhabricatorUserPreferences::PREFERENCE_TIME_FORMAT; $pref_date = PhabricatorUserPreferences::PREFERENCE_DATE_FORMAT; $pref_week_start = PhabricatorUserPreferences::PREFERENCE_WEEK_START_DAY; @@ -27,13 +28,12 @@ $errors = array(); if ($request->isFormPost()) { $new_timezone = $request->getStr('timezone'); - if (in_array($new_timezone, DateTimeZone::listIdentifiers(), true)) { - $user->setTimezoneIdentifier($new_timezone); - } else { + if (!in_array($new_timezone, DateTimeZone::listIdentifiers(), true)) { $errors[] = pht('The selected timezone is not a valid timezone.'); } $preferences + ->setPreference($pref_timezone, $new_timezone) ->setPreference( $pref_time, $request->getStr($pref_time)) @@ -47,7 +47,7 @@ if (!$errors) { $preferences->save(); - $user->save(); + return id(new AphrontRedirectResponse()) ->setURI($this->getPanelURI('?saved=true')); } diff --git a/src/applications/settings/setting/PhabricatorOptionGroupSetting.php b/src/applications/settings/setting/PhabricatorOptionGroupSetting.php new file mode 100644 --- /dev/null +++ b/src/applications/settings/setting/PhabricatorOptionGroupSetting.php @@ -0,0 +1,73 @@ +getSelectOptionGroups(); + + $map = array(); + foreach ($groups as $group) { + $map += $group['options']; + } + + return $map; + } + + final protected function newCustomEditField($object) { + $setting_key = $this->getSettingKey(); + $default_value = $object->getDefaultValue($setting_key); + + $options = $this->getSelectOptionGroups(); + + $map = $this->getSelectOptionMap(); + if (isset($map[$default_value])) { + $default_label = pht('Default (%s)', $map[$default_value]); + } else { + $default_label = pht('Default (Unknown, "%s")', $default_value); + } + + $head_key = head_key($options); + $options[$head_key]['options'] = array( + '' => $default_label, + ) + $options[$head_key]['options']; + + $flat_options = array(); + foreach ($options as $group) { + $flat_options[$group['label']] = $group['options']; + } + + return $this->newEditField($object, new PhabricatorSelectEditField()) + ->setOptions($flat_options); + } + + final public function validateTransactionValue($value) { + if (!strlen($value)) { + return; + } + + $map = $this->getSelectOptionMap(); + + if (!isset($map[$value])) { + throw new Exception( + pht( + 'Value "%s" is not valid for setting "%s": valid values are %s.', + $value, + $this->getSettingName(), + implode(', ', array_keys($map)))); + } + + return; + } + + public function getTransactionNewValue($value) { + if (!strlen($value)) { + return null; + } + + return (string)$value; + } + +} diff --git a/src/applications/settings/setting/PhabricatorPronounSetting.php b/src/applications/settings/setting/PhabricatorPronounSetting.php new file mode 100644 --- /dev/null +++ b/src/applications/settings/setting/PhabricatorPronounSetting.php @@ -0,0 +1,35 @@ +getViewer(); + $username = $viewer->getUsername(); + + $label_unknown = pht('%s updated their profile', $username); + $label_her = pht('%s updated her profile', $username); + $label_his = pht('%s updated his profile', $username); + + return array( + PhutilPerson::SEX_UNKNOWN => $label_unknown, + PhutilPerson::SEX_MALE => $label_his, + PhutilPerson::SEX_FEMALE => $label_her, + ); + } + +} diff --git a/src/applications/settings/setting/PhabricatorSelectSetting.php b/src/applications/settings/setting/PhabricatorSelectSetting.php --- a/src/applications/settings/setting/PhabricatorSelectSetting.php +++ b/src/applications/settings/setting/PhabricatorSelectSetting.php @@ -17,9 +17,11 @@ $default_label = pht('Default (Unknown, "%s")', $default_value); } - $options = array( - '' => $default_label, - ) + $options; + if (empty($options[''])) { + $options = array( + '' => $default_label, + ) + $options; + } return $this->newEditField($object, new PhabricatorSelectEditField()) ->setOptions($options); diff --git a/src/applications/settings/setting/PhabricatorTimezoneSetting.php b/src/applications/settings/setting/PhabricatorTimezoneSetting.php new file mode 100644 --- /dev/null +++ b/src/applications/settings/setting/PhabricatorTimezoneSetting.php @@ -0,0 +1,53 @@ +getOffset($now) / (60 * 60)); + $groups[$offset][] = $timezone; + } + + krsort($groups); + + $option_groups = array( + array( + 'label' => pht('Default'), + 'options' => array(), + ), + ); + + foreach ($groups as $offset => $group) { + if ($offset >= 0) { + $label = pht('UTC-%d', $offset); + } else { + $label = pht('UTC+%d', -$offset); + } + + sort($group); + $option_groups[] = array( + 'label' => $label, + 'options' => array_fuse($group), + ); + } + + return $option_groups; + } + +} diff --git a/src/applications/settings/setting/PhabricatorTranslationSetting.php b/src/applications/settings/setting/PhabricatorTranslationSetting.php new file mode 100644 --- /dev/null +++ b/src/applications/settings/setting/PhabricatorTranslationSetting.php @@ -0,0 +1,92 @@ + pht('Translations'), + 'limited' => pht('Limited Translations'), + 'silly' => pht('Silly Translations'), + 'test' => pht('Developer/Test Translations'), + ); + + $groups = array_fill_keys(array_keys($group_labels), array()); + + $translations = array(); + foreach ($locales as $locale) { + $code = $locale->getLocaleCode(); + + // Get the locale's localized name if it's available. For example, + // "Deutsch" instead of "German". This helps users who do not speak the + // current language to find the correct setting. + $raw_scope = PhabricatorEnv::beginScopedLocale($code); + $name = $locale->getLocaleName(); + unset($raw_scope); + + if ($locale->isSillyLocale()) { + if ($is_serious) { + // Omit silly locales on serious business installs. + continue; + } + $groups['silly'][$code] = $name; + continue; + } + + if ($locale->isTestLocale()) { + $groups['test'][$code] = $name; + continue; + } + + $strings = PhutilTranslation::getTranslationMapForLocale($code); + $size = count($strings); + + // If a translation is English, assume it can fall back to the default + // strings and don't caveat its completeness. + $is_english = (substr($code, 0, 3) == 'en_'); + + // Arbitrarily pick some number of available strings to promote a + // translation out of the "limited" group. The major goal is just to + // keep locales with very few strings out of the main group, so users + // aren't surprised if a locale has no upstream translations available. + if ($size > 512 || $is_english) { + $type = 'normal'; + } else { + $type = 'limited'; + } + + $groups[$type][$code] = $name; + } + + $results = array(); + foreach ($groups as $key => $group) { + $label = $group_labels[$key]; + if (!$group) { + continue; + } + + asort($group); + + $results[] = array( + 'label' => $label, + 'options' => $group, + ); + } + + return $results; + } + +} diff --git a/src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php b/src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php --- a/src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php +++ b/src/infrastructure/time/__tests__/PhabricatorTimeTestCase.php @@ -15,10 +15,10 @@ public function testParseLocalTime() { $u = new PhabricatorUser(); - $u->setTimezoneIdentifier('UTC'); + $u->overrideTimezoneIdentifier('UTC'); $v = new PhabricatorUser(); - $v->setTimezoneIdentifier('America/Los_Angeles'); + $v->overrideTimezoneIdentifier('America/Los_Angeles'); $t = 1370202281; // 2013-06-02 12:44:41 -0700 $time = PhabricatorTime::pushTime($t, 'America/Los_Angeles'); diff --git a/src/view/__tests__/PhabricatorLocalTimeTestCase.php b/src/view/__tests__/PhabricatorLocalTimeTestCase.php --- a/src/view/__tests__/PhabricatorLocalTimeTestCase.php +++ b/src/view/__tests__/PhabricatorLocalTimeTestCase.php @@ -4,10 +4,10 @@ public function testLocalTimeFormatting() { $user = new PhabricatorUser(); - $user->setTimezoneIdentifier('America/Los_Angeles'); + $user->overrideTimezoneIdentifier('America/Los_Angeles'); $utc = new PhabricatorUser(); - $utc->setTimezoneIdentifier('UTC'); + $utc->overrideTimezoneIdentifier('UTC'); $this->assertEqual( 'Jan 1 2000, 12:00 AM',