public function testFormatWithCurrentLocaleTimeZone() { $zoneId = 'Europe/Berlin'; $tm = $this->getContext()->getTranslationManager(); $tm->setDefaultTimeZone('Europe/Moscow'); $currentLocale = AgaviLocale::parseLocaleIdentifier($tm->getCurrentLocale()->getIdentifier()); $tm->setLocale($currentLocale['locale_str'] . '@timezone=' . $zoneId); $inputFormat = $tm->createDateFormat('yyy-MM-dd HH:mm:ss v'); $cal = $inputFormat->parse('2008-11-19 23:00:00 America/New_York'); $this->assertEquals('2008-11-20 05:00:00 ' . $zoneId, $inputFormat->format($cal)); }
/** * Execute this configuration handler. * * @param AgaviXmlConfigDomDocument The document to parse. * * @return string Data to be written to a cache file. * * @throws <b>AgaviParseException</b> If a requested configuration file is * improperly formatted. * * @author Dominik del Bondio <*****@*****.**> * @author David Zülke <*****@*****.**> * @since 0.11.0 */ public function execute(AgaviXmlConfigDomDocument $document) { // set up our default namespace $document->setDefaultNamespace(self::XML_NAMESPACE, 'translation'); $config = $document->documentURI; $translatorData = array(); $localeData = array(); $defaultDomain = ''; $defaultLocale = null; $defaultTimeZone = null; foreach ($document->getConfigurationElements() as $cfg) { if ($cfg->hasChild('available_locales')) { $availableLocales = $cfg->getChild('available_locales'); // TODO: is this really optional? according to the schema: yes... $defaultLocale = $availableLocales->getAttribute('default_locale', $defaultLocale); $defaultTimeZone = $availableLocales->getAttribute('default_timezone', $defaultTimeZone); foreach ($availableLocales as $locale) { $name = $locale->getAttribute('identifier'); if (!isset($localeData[$name])) { $localeData[$name] = array('name' => $name, 'params' => array(), 'fallback' => null, 'ldml_file' => null); } $localeData[$name]['params'] = $locale->getAgaviParameters($localeData[$name]['params']); $localeData[$name]['fallback'] = $locale->getAttribute('fallback', $localeData[$name]['fallback']); $localeData[$name]['ldml_file'] = $locale->getAttribute('ldml_file', $localeData[$name]['ldml_file']); } } if ($cfg->hasChild('translators')) { $translators = $cfg->getChild('translators'); $defaultDomain = $translators->getAttribute('default_domain', $defaultDomain); $this->getTranslators($translators, $translatorData); } } $data = array(); $data[] = sprintf('$this->defaultDomain = %s;', var_export($defaultDomain, true)); $data[] = sprintf('$this->defaultLocaleIdentifier = %s;', var_export($defaultLocale, true)); $data[] = sprintf('$this->defaultTimeZone = %s;', var_export($defaultTimeZone, true)); foreach ($localeData as $locale) { // TODO: fallback stuff $data[] = sprintf('$this->availableConfigLocales[%s] = array(\'identifier\' => %s, \'identifierData\' => %s, \'parameters\' => %s);', var_export($locale['name'], true), var_export($locale['name'], true), var_export(AgaviLocale::parseLocaleIdentifier($locale['name']), true), var_export($locale['params'], true)); } foreach ($translatorData as $domain => $translator) { foreach (array('msg', 'num', 'cur', 'date') as $type) { if (isset($translator[$type]['class'])) { if (!class_exists($translator[$type]['class'])) { throw new AgaviConfigurationException(sprintf('The Translator or Formatter class "%s" for domain "%s" could not be found.', $translator[$type]['class'], $domain)); } $data[] = join("\n", array(sprintf('$this->translators[%s][%s] = new %s();', var_export($domain, true), var_export($type, true), $translator[$type]['class']), sprintf('$this->translators[%s][%s]->initialize($this->getContext(), %s);', var_export($domain, true), var_export($type, true), var_export($translator[$type]['params'], true)), sprintf('$this->translatorFilters[%s][%s] = %s;', var_export($domain, true), var_export($type, true), var_export($translator[$type]['filters'], true)))); } } } return $this->generate($data, $config); }
/** * Execute this configuration handler. * * @param string An absolute filesystem path to a configuration file. * @param string An optional context in which we are currently running. * * @return string Data to be written to a cache file. * * @throws <b>AgaviUnreadableException</b> If a requested configuration * file does not exist or is not * readable. * @throws <b>AgaviParseException</b> If a requested configuration file is * improperly formatted. * * @author Dominik del Bondio <*****@*****.**> * @since 0.11.0 */ public function execute($config, $context = null) { $pathParts = pathinfo($config); // unlike basename, filename does not contain the extension, which is what we need there $lookupPaths = AgaviLocale::getLookupPath($pathParts['filename']); $lookupPaths[] = 'root'; $data = array('layout' => array('orientation' => array('lines' => 'top-to-bottom', 'characters' => 'left-to-right'))); foreach (array_reverse($lookupPaths) as $basename) { $filePath = $pathParts['dirname'] . '/' . $basename . '.' . $pathParts['extension']; if (is_readable($filePath)) { $ldmlTree = AgaviConfigCache::parseConfig($filePath, false, $this->getValidationFile(), $this->parser); $this->prepareParentInformation($ldmlTree); $this->parseLdmlTree($ldmlTree->ldml, $data); } } $dayMap = array('sun' => AgaviDateDefinitions::SUNDAY, 'mon' => AgaviDateDefinitions::MONDAY, 'tue' => AgaviDateDefinitions::TUESDAY, 'wed' => AgaviDateDefinitions::WEDNESDAY, 'thu' => AgaviDateDefinitions::THURSDAY, 'fri' => AgaviDateDefinitions::FRIDAY, 'sat' => AgaviDateDefinitions::SATURDAY); // fix the day indices for all day fields foreach ($data['calendars'] as $calKey => &$calValue) { // skip the 'default' => '' key => value pair if (is_array($calValue)) { if (isset($calValue['days']['format'])) { foreach ($calValue['days']['format'] as $formatKey => &$formatValue) { if (is_array($formatValue)) { $newData = array(); foreach ($formatValue as $day => $value) { $newData[$dayMap[$day]] = $value; } $formatValue = $newData; } } } if (isset($calValue['days']['stand-alone'])) { foreach ($calValue['days']['stand-alone'] as $formatKey => &$formatValue) { if (is_array($formatValue)) { $newData = array(); foreach ($formatValue as $day => $value) { $newData[$dayMap[$day]] = $value; } $formatValue = $newData; } } } } } $code = array(); $code[] = 'return ' . var_export($data, true) . ';'; return $this->generate($code, $config); }
private function loadDefaultFiles($tm, &$files) { $default = $tm->getDefaultDomain(); $translator = $tm->getDomainTranslator($default, AgaviTranslationManager::MESSAGE); $locale = $tm->getCurrentLocale(); if ($translator instanceof AppKitGettextTranslator) { foreach ($translator->getDomainPaths() as $domain => $path) { foreach (AgaviLocale::getLookupPath($tm->getCurrentLocale()->getIdentifier()) as $prefix) { $result = $this->loadFile($path, $prefix); if ($result) { $files[$domain] = $result; } } } } }
/** * Get the full, resolved stream location name to the template resource. * * @return string A PHP stream resource identifier. * * @throws AgaviException If the template could not be found. * * @author David Zülke <*****@*****.**> * @since 0.11.0 */ public function getResourceStreamIdentifier() { $template = $this->getParameter('template'); if ($template === null) { // no template set, we return null so nothing gets rendered return null; } $args = array(); if (AgaviConfig::get('core.use_translation')) { // i18n is enabled, build a list of sprintf args with the locale identifier foreach (AgaviLocale::getLookupPath($this->context->getTranslationManager()->getCurrentLocaleIdentifier()) as $identifier) { $args[] = array('locale' => $identifier); } } if (empty($args)) { $args[] = array(); // add one empty arg to always trigger target lookups (even if i18n is disabled etc.) } $scheme = $this->getParameter('scheme'); // FIXME: a simple workaround for broken ubuntu and debian packages (fixed already), we can remove that for final 0.11 if ($scheme != 'file' && !in_array($scheme, stream_get_wrappers())) { throw new AgaviException('Unknown stream wrapper "' . $scheme . '", must be one of "' . implode('", "', stream_get_wrappers()) . '".'); } $check = $this->getParameter('check'); $attempts = array(); // try each of the patterns foreach ((array) $this->getParameter('targets', array()) as $pattern) { // try pattern with each argument list foreach ($args as $arg) { $target = AgaviToolkit::expandVariables($pattern, array_merge(array_filter($this->getParameters(), 'is_scalar'), array_filter($this->getParameters(), 'is_null'), $arg)); // FIXME (should they fix it): don't add file:// because suhosin's include whitelist is empty by default, does not contain 'file' as allowed uri scheme if ($scheme != 'file') { $target = $scheme . '://' . $target; } if (!$check || is_readable($target)) { return $target; } $attempts[] = $target; } } // no template found, time to throw an exception throw new AgaviException('Template "' . $template . '" could not be found. Paths tried:' . "\n" . implode("\n", $attempts)); }
/** * Loads the data from the data file for the given domain with the current * locale. * * @param string The domain to load the data for. * * @author Dominik del Bondio <*****@*****.**> * @since 0.11.0 */ public function loadDomainData($domain) { $localeName = $this->locale->getIdentifier(); $localeNameBases = AgaviLocale::getLookupPath($localeName); if (!isset($this->domainPaths[$domain])) { if (!$this->domainPathPattern) { throw new AgaviException('Using domain "' . $domain . '" which has no path specified'); } else { $basePath = $this->domainPathPattern; } } else { $basePath = $this->domainPaths[$domain]; } $basePath = AgaviToolkit::expandVariables($basePath, array('domain' => $domain)); $data = array(); foreach ($localeNameBases as $localeNameBase) { $fileName = AgaviToolkit::expandVariables($basePath, array('locale' => $localeNameBase)); if ($fileName === $basePath) { // no replacing of $locale happened $fileName = $basePath . '/' . $localeNameBase . '.mo'; } if (is_readable($fileName)) { $fileData = AgaviGettextMoReader::readFile($fileName); // instead of array_merge, which doesn't handle null bytes in keys properly. careful, the order matters here. $data = $fileData + $data; } } $headers = array(); if (count($data)) { $headerData = str_replace("\r", '', $data['']); $headerLines = explode("\n", $headerData); foreach ($headerLines as $line) { $values = explode(':', $line, 2); // skip empty / invalid lines if (count($values) == 2) { $headers[$values[0]] = $values[1]; } } } $this->pluralFormFunc = null; if (isset($headers['Plural-Forms'])) { $pf = $headers['Plural-Forms']; if (preg_match('#nplurals=\\d+;\\s+plural=(.*)$#D', $pf, $match)) { $funcCode = $match[1]; $validOpChars = array(' ', 'n', '!', '&', '|', '<', '>', '(', ')', '?', ':', ';', '=', '+', '*', '/', '%', '-'); if (preg_match('#[^\\d' . preg_quote(implode('', $validOpChars), '#') . ']#', $funcCode, $errorMatch)) { throw new AgaviException('Illegal character ' . $errorMatch[0] . ' in plural form ' . $funcCode); } // add parenthesis around all ternary expressions. This is done // to make the ternary operator (?) have precedence over the delimiter (:) // This will transform // "a ? 1 : b ? c ? 3 : 4 : 2" to "(a ? 1 : (b ? (c ? 3 : 4) : 2))" and // "a ? b ? c ? d ? 5 : 4 : 3 : 2 : 1" to "(a ? (b ? (c ? (d ? 5 : 4) : 3) : 2) : 1)" // "a ? b ? c ? 4 : 3 : d ? 5 : 2 : 1" to "(a ? (b ? (c ? 4 : 3) : (d ? 5 : 2)) : 1)" // "a ? b ? c ? 4 : 3 : d ? 5 : e ? 6 : 2 : 1" to "(a ? (b ? (c ? 4 : 3) : (d ? 5 : (e ? 6 : 2))) : 1)" $funcCode = rtrim($funcCode, ';'); $parts = preg_split('#(\\?|\\:)#', $funcCode, -1, PREG_SPLIT_DELIM_CAPTURE); $parenthesisCount = 0; $unclosedParenthesisCount = 0; $firstParenthesis = true; $funcCode = ''; for ($i = 0, $c = count($parts); $i < $c; ++$i) { $lastPart = $i > 0 ? $parts[$i - 1] : null; $part = $parts[$i]; $nextPart = $i + 1 < $c ? $parts[$i + 1] : null; if ($nextPart == '?') { if ($lastPart == ':') { // keep track of parenthesis which need to be closed // directly after this ternary expression ++$unclosedParenthesisCount; --$parenthesisCount; } $funcCode .= ' (' . $part; ++$parenthesisCount; } elseif ($lastPart == ':') { $funcCode .= $part . ') '; if ($unclosedParenthesisCount > 0) { $funcCode .= str_repeat(')', $unclosedParenthesisCount); $unclosedParenthesisCount = 0; } --$parenthesisCount; } else { $funcCode .= $part; } } if ($parenthesisCount > 0) { // add the missing top level parenthesis $funcCode .= str_repeat(')', $parenthesisCount); } $funcCode .= ';'; $funcCode = 'return ' . str_replace('n', '$n', $funcCode); $this->pluralFormFunc = create_function('$n', $funcCode); } } $this->domainData[$domain] = array('headers' => $headers, 'msgs' => $data); }
protected static function getDecimalParseRegex(AgaviLocale $locale = null) { static $patternCache = array(); if ($locale) { $localeId = $locale->getIdentifier(); } else { $localeId = ''; } if (isset($patternCache[$localeId])) { return $patternCache[$localeId]; } if ($locale) { $decimalFormats = $locale->getDecimalFormats(); $groupingSeparator = $locale->getNumberSymbolGroup(); $decimalSeparator = $locale->getNumberSymbolDecimal(); $minusSign = $locale->getNumberSymbolMinusSign(); } else { $decimalFormats = array('#,##0.###'); $groupingSeparator = ','; $decimalSeparator = '.'; $minusSign = '-'; } $patterns = array(); foreach ($decimalFormats as $decimalFormatList) { $decimalFormatList = explode(';', $decimalFormatList, 2); if (count($decimalFormatList) == 1) { // no pattern for negative numbers // we need a copy of the format with a minus prefix $decimalFormatList[1] = '-' . $decimalFormatList[0]; } foreach (array(true, false) as $withFraction) { foreach ($decimalFormatList as $decimalFormat) { // we need to make three parts: number, decimal part and minus sign $decimalFormatChunks = preg_split('/([\\.\\-])/', $decimalFormat, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); // there is a minus sign at the beginning or end! find it! $pastDecimalSeparator = false; foreach ($decimalFormatChunks as &$decimalFormatChunk) { if ($decimalFormatChunk == '-') { // always allow "-" in addition to the minus sign supplied by the locale. we do this because some locales (e.g. da, fa, se, sv) have U+2212 (the "real" minus sign) defined in the locale data, but no human can really type that character on their keyboard. consider it lenient parsing ;) see ticket #1293 $decimalFormatChunk = '(?P<minus>' . preg_quote($minusSign, '#') . '|-)'; } elseif ($decimalFormatChunk == '.') { $pastDecimalSeparator = true; if ($withFraction) { $decimalFormatChunk = preg_quote($decimalSeparator, '#'); } else { $decimalFormatChunk = ''; } } else { $decimalFormatChunk = preg_replace('/[#0,]+/u', '[\\d' . preg_quote($groupingSeparator, '#') . ']*', $decimalFormatChunk); if (!$pastDecimalSeparator) { if ($withFraction) { $decimalFormatChunk = '(?P<num>(?=[\\d,]*\\d)' . $decimalFormatChunk . '(\\d|' . $decimalFormatChunk . '(?=\\.[\\d,]*\\d)))?'; } else { $decimalFormatChunk = '(?P<num>(?=[\\d,]*\\d)' . $decimalFormatChunk . '\\d)'; } } else { if ($withFraction) { $decimalFormatChunk = '(?P<dec>' . $decimalFormatChunk . '\\d)?'; } else { $decimalFormatChunk = ''; } } } } $patterns[] = implode('', $decimalFormatChunks); } } } return $patternCache[$localeId] = '#(?J)^(' . implode('|', $patterns) . ')#u'; }
/** * This method gets called by the translation manager when the default locale * has been changed. * * @param string The new default locale. * * @author Dominik del Bondio <*****@*****.**> * @author David Zülke <*****@*****.**> * @since 0.11.0 */ public function localeChanged($newLocale) { $this->locale = $newLocale; $this->groupingSeparator = $this->locale->getNumberSymbolGroup(); $this->decimalSeparator = $this->locale->getNumberSymbolDecimal(); $format = $this->locale->getCurrencyFormat('__default'); if (is_array($this->customFormat)) { $format = AgaviToolkit::getValueByKeyList($this->customFormat, AgaviLocale::getLookupPath($this->locale->getIdentifier()), $format); } elseif ($this->customFormat) { $format = $this->customFormat; } $this->setFormat($format); }
/** * This method gets called by the translation manager when the default locale * has been changed. * * @param string The new default locale. * * @author Dominik del Bondio <*****@*****.**> * @author David Zülke <*****@*****.**> * @since 0.11.0 */ public function localeChanged($newLocale) { $this->locale = $newLocale; $format = null; // ze default if (is_array($this->customFormat)) { $format = AgaviToolkit::getValueByKeyList($this->customFormat, AgaviLocale::getLookupPath($this->locale->getIdentifier())); } elseif ($this->customFormat && !$this->translationDomain) { $format = $this->customFormat; } if ($format === null || $this->isDateSpecifier($format)) { $format = $this->resolveSpecifier($this->locale, $format, $this->type); } $this->setFormat($format); }
/** * Returns a new AgaviLocale object from the given identifier. * * @param string The locale identifier * @param bool Force a new instance even if an identical one exists. * * @return AgaviLocale The locale instance which matches the available * locales most. * * @author Dominik del Bondio <*****@*****.**> * @author David Zülke <*****@*****.**> * @since 0.11.0 */ public function getLocale($identifier, $forceNew = false) { // enable shortcut notation to only set options to the current locale if ($identifier[0] == '@' && $this->currentLocaleIdentifier) { $idData = AgaviLocale::parseLocaleIdentifier($this->currentLocaleIdentifier); $identifier = $idData['locale_str'] . $identifier; $newIdData = AgaviLocale::parseLocaleIdentifier($identifier); $idData['options'] = array_merge($idData['options'], $newIdData['options']); } else { $idData = AgaviLocale::parseLocaleIdentifier($identifier); } // this doesn't care about the options $availableLocale = $this->availableLocales[$this->getLocaleIdentifier($identifier)]; // if the user wants all options reset he supplies an 'empty' option set (identifier ends with @) if (substr($identifier, -1) == '@') { $idData['options'] = array(); } else { $idData['options'] = array_merge($availableLocale['identifierData']['options'], $idData['options']); } if (($atPos = strpos($identifier, '@')) !== false) { $identifier = $availableLocale['identifierData']['locale_str'] . substr($identifier, $atPos); } else { $identifier = $availableLocale['identifier']; } if (!$forceNew && isset($this->localeCache[$identifier])) { return $this->localeCache[$identifier]; } if (!isset($this->localeDataCache[$idData['locale_str']])) { $lookupPath = AgaviLocale::getLookupPath($availableLocale['identifierData']); $cldrDir = AgaviConfig::get('core.cldr_dir'); $data = null; foreach ($lookupPath as $localeName) { $fileName = $cldrDir . '/locales/' . $localeName . '.xml'; if (is_readable($fileName)) { $data = (include AgaviConfigCache::checkConfig($fileName)); break; } } if ($data === null) { throw new AgaviException('No data available for locale ' . $identifier); } if ($availableLocale['identifierData']['territory']) { $territory = $availableLocale['identifierData']['territory']; if (isset($this->supplementalData['territories'][$territory]['currencies'])) { $slice = array_slice($this->supplementalData['territories'][$territory]['currencies'], 0, 1); $currency = current($slice); $data['locale']['currency'] = $currency['currency']; } } $this->localeDataCache[$idData['locale_str']] = $data; } $data = $this->localeDataCache[$idData['locale_str']]; if (isset($idData['options']['calendar'])) { $data['locale']['calendar'] = $idData['options']['calendar']; } if (isset($idData['options']['currency'])) { $data['locale']['currency'] = $idData['options']['currency']; } if (isset($idData['options']['timezone'])) { $data['locale']['timezone'] = $idData['options']['timezone']; } $locale = new AgaviLocale(); $locale->initialize($this->context, $availableLocale['parameters'], $identifier, $data); if (!$forceNew) { $this->localeCache[$identifier] = $locale; } return $locale; }
protected function doYEAR_WOYLoop($cal, $sdf, $times) { $tstres = new AgaviGregorianCalendar(AgaviLocale::getGermany()); for ($i = 0; $i < $times; ++$i) { $tstres = clone $cal; $tstres->clear(); $tstres->set(AgaviDateDefinitions::YEAR_WOY, $cal->get(AgaviDateDefinitions::YEAR_WOY)); $tstres->set(AgaviDateDefinitions::WEEK_OF_YEAR, $cal->get(AgaviDateDefinitions::WEEK_OF_YEAR)); $tstres->set(AgaviDateDefinitions::DOW_LOCAL, $cal->get(AgaviDateDefinitions::DOW_LOCAL)); $this->assertEquals($cal->get(AgaviDateDefinitions::YEAR), $tstres->get(AgaviDateDefinitions::YEAR)); $this->assertEquals($cal->get(AgaviDateDefinitions::DAY_OF_YEAR), $tstres->get(AgaviDateDefinitions::DAY_OF_YEAR)); $cal->add(AgaviDateDefinitions::DATE, 1, errorCode); } }
/** * This method gets called by the translation manager when the default locale * has been changed. * * @param AgaviLocale The new default locale. * * @author David Zülke <*****@*****.**> * @since 0.11.0 */ public function localeChanged($newLocale) { $this->locale = $newLocale; $this->currentData = AgaviToolkit::getValueByKeyList($this->domainData, AgaviLocale::getLookupPath($this->locale->getIdentifier()), array()); }
/** * Returns the locale option string containing the timezone option set * to the timezone of this calendar. * * @param string The prefix which will be applied to the timezone option * string. Use ';' here if you intend to use several * locale options and append the result of this method * to your locale string. * * @return string * * @author Dominik del Bondio <*****@*****.**> * @since 1.0.0 */ public function getTimeZoneLocaleOptionString($prefix = '@') { return AgaviLocale::getTimeZoneOptionString($this, $prefix); }