From a7537906b4e6e370e5911448f41d6d2552f6832b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 27 Sep 2010 09:45:29 +0200 Subject: [PATCH] [Translation] added the component --- .../Translation/Loader/ArrayLoader.php | 33 + .../Translation/Loader/LoaderInterface.php | 33 + .../Translation/Loader/PhpFileLoader.php | 35 + .../Translation/Loader/XliffFileLoader.php | 96 + .../dic/xliff-core/xliff-core-1.2-strict.xsd | 2223 +++++++++++++++++ .../Loader/schema/dic/xliff-core/xml.xsd | 309 +++ .../Translation/MessageCatalogue.php | 145 ++ .../Translation/MessageCatalogueInterface.php | 113 + .../Translation/PluralizationRules.php | 214 ++ src/Symfony/Component/Translation/Range.php | 102 + .../Translation/Resource/FileResource.php | 68 + .../Resource/ResourceInterface.php | 43 + .../Component/Translation/Translator.php | 201 ++ .../Translation/TranslatorInterface.php | 52 + .../Tests/Component/Translation/RangeTest.php | 48 + .../Component/Translation/TranslatorTest.php | 71 + 16 files changed, 3786 insertions(+) create mode 100644 src/Symfony/Component/Translation/Loader/ArrayLoader.php create mode 100644 src/Symfony/Component/Translation/Loader/LoaderInterface.php create mode 100644 src/Symfony/Component/Translation/Loader/PhpFileLoader.php create mode 100644 src/Symfony/Component/Translation/Loader/XliffFileLoader.php create mode 100644 src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd create mode 100644 src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xml.xsd create mode 100644 src/Symfony/Component/Translation/MessageCatalogue.php create mode 100644 src/Symfony/Component/Translation/MessageCatalogueInterface.php create mode 100644 src/Symfony/Component/Translation/PluralizationRules.php create mode 100644 src/Symfony/Component/Translation/Range.php create mode 100644 src/Symfony/Component/Translation/Resource/FileResource.php create mode 100644 src/Symfony/Component/Translation/Resource/ResourceInterface.php create mode 100644 src/Symfony/Component/Translation/Translator.php create mode 100644 src/Symfony/Component/Translation/TranslatorInterface.php create mode 100644 tests/Symfony/Tests/Component/Translation/RangeTest.php create mode 100644 tests/Symfony/Tests/Component/Translation/TranslatorTest.php diff --git a/src/Symfony/Component/Translation/Loader/ArrayLoader.php b/src/Symfony/Component/Translation/Loader/ArrayLoader.php new file mode 100644 index 0000000000..6c0f295ce2 --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/ArrayLoader.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ArrayLoader loads translations from a PHP array. + * + * @author Fabien Potencier + */ +class ArrayLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + function load($resource, $locale, $domain = 'messages') + { + $catalogue = new MessageCatalogue($locale); + $catalogue->addMessages($resource, $domain); + + return $catalogue; + } +} diff --git a/src/Symfony/Component/Translation/Loader/LoaderInterface.php b/src/Symfony/Component/Translation/Loader/LoaderInterface.php new file mode 100644 index 0000000000..baf503fe7e --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/LoaderInterface.php @@ -0,0 +1,33 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * LoaderInterface is the interface implemented by all translation loaders. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a locale. + * + * @param mixed $resource A resource + * @param string $locale A locale + * @param string $domain The domain + * + * @return MessageCatalogue A MessageCatalogue instance + */ + function load($resource, $locale, $domain = 'messages'); +} diff --git a/src/Symfony/Component/Translation/Loader/PhpFileLoader.php b/src/Symfony/Component/Translation/Loader/PhpFileLoader.php new file mode 100644 index 0000000000..52eec2232b --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/PhpFileLoader.php @@ -0,0 +1,35 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * PhpFileLoader loads translations from PHP files returning an array of translations. + * + * @author Fabien Potencier + */ +class PhpFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + function load($resource, $locale, $domain = 'messages') + { + $catalogue = new MessageCatalogue($locale); + $catalogue->addMessages(require($resource), $domain); + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } +} diff --git a/src/Symfony/Component/Translation/Loader/XliffFileLoader.php b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php new file mode 100644 index 0000000000..67c276088d --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/XliffFileLoader.php @@ -0,0 +1,96 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * XliffFileLoader loads translations from XLIFF files. + * + * @author Fabien Potencier + */ +class XliffFileLoader implements LoaderInterface +{ + /** + * {@inheritdoc} + */ + function load($resource, $locale, $domain = 'messages') + { + $xml = $this->parseFile($resource); + $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2'); + + $catalogue = new MessageCatalogue($locale); + foreach ($xml->xpath('//xliff:trans-unit') as $translation) { + $catalogue->setMessage((string) $translation->source, (string) $translation->target, $domain); + } + $catalogue->addResource(new FileResource($resource)); + + return $catalogue; + } + + /** + * Validates and parses the given file into a SimpleXMLElement + * + * @param string $file + * @return SimpleXMLElement + */ + protected function parseFile($file) + { + $dom = new \DOMDocument(); + $current = libxml_use_internal_errors(true); + if (!@$dom->load($file, LIBXML_COMPACT)) { + throw new \Exception(implode("\n", $this->getXmlErrors())); + } + + $parts = explode('/', str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd'); + $drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $location = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts)); + + $source = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd'); + $source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source); + + if (!@$dom->schemaValidateSource($source)) { + throw new \Exception(implode("\n", $this->getXmlErrors())); + } + $dom->validateOnParse = true; + $dom->normalizeDocument(); + libxml_use_internal_errors($current); + + return simplexml_import_dom($dom); + } + + /** + * Returns the XML errors of the internal XML parser + * + * @return array An array of errors + */ + protected function getXmlErrors() + { + $errors = array(); + foreach (libxml_get_errors() as $error) { + $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', + LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + $error->code, + trim($error->message), + $error->file ? $error->file : 'n/a', + $error->line, + $error->column + ); + } + + libxml_clear_errors(); + libxml_use_internal_errors(false); + + return $errors; + } +} diff --git a/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd b/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd new file mode 100644 index 0000000000..3ce2a8e8ab --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xliff-core-1.2-strict.xsd @@ -0,0 +1,2223 @@ + + + + + + + + + + + + + + + Values for the attribute 'context-type'. + + + + + Indicates a database content. + + + + + Indicates the content of an element within an XML document. + + + + + Indicates the name of an element within an XML document. + + + + + Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found. + + + + + Indicates a the number of parameters contained within the <source>. + + + + + Indicates notes pertaining to the parameters in the <source>. + + + + + Indicates the content of a record within a database. + + + + + Indicates the name of a record within a database. + + + + + Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file. + + + + + + + Values for the attribute 'count-type'. + + + + + Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts. + + + + + Indicates the count units are translation units existing already in the same document. + + + + + Indicates a total count. + + + + + + + Values for the attribute 'ctype' when used other elements than <ph> or <x>. + + + + + Indicates a run of bolded text. + + + + + Indicates a run of text in italics. + + + + + Indicates a run of underlined text. + + + + + Indicates a run of hyper-text. + + + + + + + Values for the attribute 'ctype' when used with <ph> or <x>. + + + + + Indicates a inline image. + + + + + Indicates a page break. + + + + + Indicates a line break. + + + + + + + + + + + + Values for the attribute 'datatype'. + + + + + Indicates Active Server Page data. + + + + + Indicates C source file data. + + + + + Indicates Channel Definition Format (CDF) data. + + + + + Indicates ColdFusion data. + + + + + Indicates C++ source file data. + + + + + Indicates C-Sharp data. + + + + + Indicates strings from C, ASM, and driver files data. + + + + + Indicates comma-separated values data. + + + + + Indicates database data. + + + + + Indicates portions of document that follows data and contains metadata. + + + + + Indicates portions of document that precedes data and contains metadata. + + + + + Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import). + + + + + Indicates standard user input screen data. + + + + + Indicates HyperText Markup Language (HTML) data - document instance. + + + + + Indicates content within an HTML document’s <body> element. + + + + + Indicates Windows INI file data. + + + + + Indicates Interleaf data. + + + + + Indicates Java source file data (extension '.java'). + + + + + Indicates Java property resource bundle data. + + + + + Indicates Java list resource bundle data. + + + + + Indicates JavaScript source file data. + + + + + Indicates JScript source file data. + + + + + Indicates information relating to formatting. + + + + + Indicates LISP source file data. + + + + + Indicates information relating to margin formats. + + + + + Indicates a file containing menu. + + + + + Indicates numerically identified string table. + + + + + Indicates Maker Interchange Format (MIF) data. + + + + + Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute. + + + + + Indicates GNU Machine Object data. + + + + + Indicates Message Librarian strings created by Novell's Message Librarian Tool. + + + + + Indicates information to be displayed at the bottom of each page of a document. + + + + + Indicates information to be displayed at the top of each page of a document. + + + + + Indicates a list of property values (e.g., settings within INI files or preferences dialog). + + + + + Indicates Pascal source file data. + + + + + Indicates Hypertext Preprocessor data. + + + + + Indicates plain text file (no formatting other than, possibly, wrapping). + + + + + Indicates GNU Portable Object file. + + + + + Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc. + + + + + Indicates Windows .NET binary resources. + + + + + Indicates Windows .NET Resources. + + + + + Indicates Rich Text Format (RTF) data. + + + + + Indicates Standard Generalized Markup Language (SGML) data - document instance. + + + + + Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD). + + + + + Indicates Scalable Vector Graphic (SVG) data. + + + + + Indicates VisualBasic Script source file. + + + + + Indicates warning message. + + + + + Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file). + + + + + Indicates Extensible HyperText Markup Language (XHTML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - document instance. + + + + + Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD). + + + + + Indicates Extensible Stylesheet Language (XSL) data. + + + + + Indicates XUL elements. + + + + + + + Values for the attribute 'mtype'. + + + + + Indicates the marked text is an abbreviation. + + + + + ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept. + + + + + ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective'). + + + + + ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging'). + + + + + ISO-12620: A proper-name term, such as the name of an agency or other proper entity. + + + + + ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another. + + + + + ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language. + + + + + Indicates the marked text is a date and/or time. + + + + + ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign. + + + + + ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form. + + + + + ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula. + + + + + ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record. + + + + + ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy'). + + + + + ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body. + + + + + ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages. + + + + + ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like. + + + + + ISO-12620 2.1.17: A unit to track object. + + + + + Indicates the marked text is a name. + + + + + ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others. + + + + + ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system. + + + + + Indicates the marked text is a phrase. + + + + + ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase. + + + + + Indicates the marked text should not be translated. + + + + + ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet. + + + + + Indicates that the marked text represents a segment. + + + + + ISO-12620 2.1.18.2: A fixed, lexicalized phrase. + + + + + ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs'). + + + + + ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system. + + + + + ISO-12620 2.1.19: A fixed chunk of recurring text. + + + + + ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof. + + + + + ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry. + + + + + ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language. + + + + + Indicates the marked text is a term. + + + + + ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted. + + + + + ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system. + + + + + ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza'). + + + + + ISO-12620 2.1.9: One of the alternate forms of a term. + + + + + + + Values for the attribute 'restype'. + + + + + Indicates a Windows RC AUTO3STATE control. + + + + + Indicates a Windows RC AUTOCHECKBOX control. + + + + + Indicates a Windows RC AUTORADIOBUTTON control. + + + + + Indicates a Windows RC BEDIT control. + + + + + Indicates a bitmap, for example a BITMAP resource in Windows. + + + + + Indicates a button object, for example a BUTTON control Windows. + + + + + Indicates a caption, such as the caption of a dialog box. + + + + + Indicates the cell in a table, for example the content of the <td> element in HTML. + + + + + Indicates check box object, for example a CHECKBOX control in Windows. + + + + + Indicates a menu item with an associated checkbox. + + + + + Indicates a list box, but with a check-box for each item. + + + + + Indicates a color selection dialog. + + + + + Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows. + + + + + Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234). + + + + + Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403). + + + + + Indicates a UI base class element that cannot be represented by any other element. + + + + + Indicates a context menu. + + + + + Indicates a Windows RC CTEXT control. + + + + + Indicates a cursor, for example a CURSOR resource in Windows. + + + + + Indicates a date/time picker. + + + + + Indicates a Windows RC DEFPUSHBUTTON control. + + + + + Indicates a dialog box. + + + + + Indicates a Windows RC DLGINIT resource block. + + + + + Indicates an edit box object, for example an EDIT control in Windows. + + + + + Indicates a filename. + + + + + Indicates a file dialog. + + + + + Indicates a footnote. + + + + + Indicates a font name. + + + + + Indicates a footer. + + + + + Indicates a frame object. + + + + + Indicates a XUL grid element. + + + + + Indicates a groupbox object, for example a GROUPBOX control in Windows. + + + + + Indicates a header item. + + + + + Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML. + + + + + Indicates a Windows RC HEDIT control. + + + + + Indicates a horizontal scrollbar. + + + + + Indicates an icon, for example an ICON resource in Windows. + + + + + Indicates a Windows RC IEDIT control. + + + + + Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF. + + + + + Indicates a label object. + + + + + Indicates a label that is also a HTML link (not necessarily a URL). + + + + + Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML). + + + + + Indicates a listbox object, for example an LISTBOX control in Windows. + + + + + Indicates an list item (an entry in a list). + + + + + Indicates a Windows RC LTEXT control. + + + + + Indicates a menu (a group of menu-items). + + + + + Indicates a toolbar containing one or more tope level menus. + + + + + Indicates a menu item (an entry in a menu). + + + + + Indicates a XUL menuseparator element. + + + + + Indicates a message, for example an entry in a MESSAGETABLE resource in Windows. + + + + + Indicates a calendar control. + + + + + Indicates an edit box beside a spin control. + + + + + Indicates a catch all for rectangular areas. + + + + + Indicates a standalone menu not necessarily associated with a menubar. + + + + + Indicates a pushbox object, for example a PUSHBOX control in Windows. + + + + + Indicates a Windows RC PUSHBUTTON control. + + + + + Indicates a radio button object. + + + + + Indicates a menuitem with associated radio button. + + + + + Indicates raw data resources for an application. + + + + + Indicates a row in a table. + + + + + Indicates a Windows RC RTEXT control. + + + + + Indicates a user navigable container used to show a portion of a document. + + + + + Indicates a generic divider object (e.g. menu group separator). + + + + + Windows accelerators, shortcuts in resource or property files. + + + + + Indicates a UI control to indicate process activity but not progress. + + + + + Indicates a splitter bar. + + + + + Indicates a Windows RC STATE3 control. + + + + + Indicates a window for providing feedback to the users, like 'read-only', etc. + + + + + Indicates a string, for example an entry in a STRINGTABLE resource in Windows. + + + + + Indicates a layers of controls with a tab to select layers. + + + + + Indicates a display and edits regular two-dimensional tables of cells. + + + + + Indicates a XUL textbox element. + + + + + Indicates a UI button that can be toggled to on or off state. + + + + + Indicates an array of controls, usually buttons. + + + + + Indicates a pop up tool tip text. + + + + + Indicates a bar with a pointer indicating a position within a certain range. + + + + + Indicates a control that displays a set of hierarchical data. + + + + + Indicates a URI (URN or URL). + + + + + Indicates a Windows RC USERBUTTON control. + + + + + Indicates a user-defined control like CONTROL control in Windows. + + + + + Indicates the text of a variable. + + + + + Indicates version information about a resource like VERSIONINFO in Windows. + + + + + Indicates a vertical scrollbar. + + + + + Indicates a graphical window. + + + + + + + Values for the attribute 'size-unit'. + + + + + Indicates a size in 8-bit bytes. + + + + + Indicates a size in Unicode characters. + + + + + Indicates a size in columns. Used for HTML text area. + + + + + Indicates a size in centimeters. + + + + + Indicates a size in dialog units, as defined in Windows resources. + + + + + Indicates a size in 'font-size' units (as defined in CSS). + + + + + Indicates a size in 'x-height' units (as defined in CSS). + + + + + Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster' + + + + + Indicates a size in inches. + + + + + Indicates a size in millimeters. + + + + + Indicates a size in percentage. + + + + + Indicates a size in pixels. + + + + + Indicates a size in point. + + + + + Indicates a size in rows. Used for HTML text area. + + + + + + + Values for the attribute 'state'. + + + + + Indicates the terminating state. + + + + + Indicates only non-textual information needs adaptation. + + + + + Indicates both text and non-textual information needs adaptation. + + + + + Indicates only non-textual information needs review. + + + + + Indicates both text and non-textual information needs review. + + + + + Indicates that only the text of the item needs to be reviewed. + + + + + Indicates that the item needs to be translated. + + + + + Indicates that the item is new. For example, translation units that were not in a previous version of the document. + + + + + Indicates that changes are reviewed and approved. + + + + + Indicates that the item has been translated. + + + + + + + Values for the attribute 'state-qualifier'. + + + + + Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously. + + + + + Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.). + + + + + Indicates a match based on matching IDs (in addition to matching text). + + + + + Indicates a translation derived from a glossary. + + + + + Indicates a translation derived from existing translation. + + + + + Indicates a translation derived from machine translation. + + + + + Indicates a translation derived from a translation repository. + + + + + Indicates a translation derived from a translation memory. + + + + + Indicates the translation is suggested by machine translation. + + + + + Indicates that the item has been rejected because of incorrect grammar. + + + + + Indicates that the item has been rejected because it is incorrect. + + + + + Indicates that the item has been rejected because it is too long or too short. + + + + + Indicates that the item has been rejected because of incorrect spelling. + + + + + Indicates the translation is suggested by translation memory. + + + + + + + Values for the attribute 'unit'. + + + + + Refers to words. + + + + + Refers to pages. + + + + + Refers to <trans-unit> elements. + + + + + Refers to <bin-unit> elements. + + + + + Refers to glyphs. + + + + + Refers to <trans-unit> and/or <bin-unit> elements. + + + + + Refers to the occurrences of instances defined by the count-type value. + + + + + Refers to characters. + + + + + Refers to lines. + + + + + Refers to sentences. + + + + + Refers to paragraphs. + + + + + Refers to segments. + + + + + Refers to placeables (inline elements). + + + + + + + Values for the attribute 'priority'. + + + + + Highest priority. + + + + + High priority. + + + + + High priority, but not as important as 2. + + + + + High priority, but not as important as 3. + + + + + Medium priority, but more important than 6. + + + + + Medium priority, but less important than 5. + + + + + Low priority, but more important than 8. + + + + + Low priority, but more important than 9. + + + + + Low priority. + + + + + Lowest priority. + + + + + + + + + This value indicates that all properties can be reformatted. This value must be used alone. + + + + + This value indicates that no properties should be reformatted. This value must be used alone. + + + + + + + + + + + + + This value indicates that all information in the coord attribute can be modified. + + + + + This value indicates that the x information in the coord attribute can be modified. + + + + + This value indicates that the y information in the coord attribute can be modified. + + + + + This value indicates that the cx information in the coord attribute can be modified. + + + + + This value indicates that the cy information in the coord attribute can be modified. + + + + + This value indicates that all the information in the font attribute can be modified. + + + + + This value indicates that the name information in the font attribute can be modified. + + + + + This value indicates that the size information in the font attribute can be modified. + + + + + This value indicates that the weight information in the font attribute can be modified. + + + + + This value indicates that the information in the css-style attribute can be modified. + + + + + This value indicates that the information in the style attribute can be modified. + + + + + This value indicates that the information in the exstyle attribute can be modified. + + + + + + + + + + + + + Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document. + + + + + Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed. + + + + + Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed. + + + + + + + + + Represents a translation proposal from a translation memory or other resource. + + + + + Represents a previous version of the target element. + + + + + Represents a rejected version of the target element. + + + + + Represents a translation to be used for reference purposes only, for example from a related product or a different language. + + + + + Represents a proposed translation that was used for the translation of the trans-unit, possibly modified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Values for the attribute 'coord'. + + + + + + + + Version values: 1.0 and 1.1 are allowed for backward compatibility. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xml.xsd b/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xml.xsd new file mode 100644 index 0000000000..282cb5be90 --- /dev/null +++ b/src/Symfony/Component/Translation/Loader/schema/dic/xliff-core/xml.xsd @@ -0,0 +1,309 @@ + + + + + + +
+

About the XML namespace

+ +
+

+ + This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+ +

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+ +
+
+ + + + +
+ +

lang (as an attribute name)

+

+ + denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ + See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ + The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + + +
+ + + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + + +
+ + + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+ +
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ + denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+ +
+ + + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ + In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+ +

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+          <schema . . .>
+           . . .
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+     
+

+ or +

+
+
+           <import namespace="http://www.w3.org/XML/1998/namespace"
+                      schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+     
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+          <type . . .>
+           . . .
+           <attributeGroup ref="xml:specialAttrs"/>
+     
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+ +
+
+
+
+ + + +
+

Versioning policy for this schema document

+ +
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+ +

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ + Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ +
diff --git a/src/Symfony/Component/Translation/MessageCatalogue.php b/src/Symfony/Component/Translation/MessageCatalogue.php new file mode 100644 index 0000000000..26c62e9b95 --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCatalogue.php @@ -0,0 +1,145 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * MessageCatalogue. + * + * @author Fabien Potencier + */ +class MessageCatalogue implements MessageCatalogueInterface +{ + protected $messages = array(); + protected $locale; + protected $resources; + + /** + * Constructor. + * + * @param string $locale The locale + * @param array $messages An array of messages classified by domain + */ + public function __construct($locale, array $messages = array()) + { + $this->locale = $locale; + $this->messages = $messages; + $this->resources = array(); + } + + /** + * {@inheritdoc} + */ + public function getLocale() + { + return $this->locale; + } + + /** + * {@inheritdoc} + */ + public function getDomains() + { + return array_keys($this->messages); + } + + /** + * {@inheritdoc} + */ + public function getMessages($domain = null) + { + if (null === $domain) { + return $this->messages; + } + + return isset($this->messages[$domain]) ? $this->messages[$domain] : array(); + } + + /** + * {@inheritdoc} + */ + public function setMessage($id, $translation, $domain = 'messages') + { + $this->addMessages(array($id => $translation), $domain); + } + + /** + * {@inheritdoc} + */ + public function hasMessage($id, $domain = 'messages') + { + return isset($this->messages[$domain][$id]); + } + + /** + * {@inheritdoc} + */ + public function getMessage($id, $domain = 'messages') + { + return isset($this->messages[$domain][$id]) ? $this->messages[$domain][$id] : $id; + } + + /** + * {@inheritdoc} + */ + public function setMessages($messages, $domain = 'messages') + { + if (isset($this->messages[$domain])) { + $this->messages[$domain] = array(); + } + + $this->addMessages($messages, $domain); + } + + /** + * {@inheritdoc} + */ + public function addMessages($messages, $domain = 'messages') + { + if (!isset($this->messages[$domain])) { + $this->messages[$domain] = $messages; + } else { + $this->messages[$domain] = array_replace($this->messages[$domain], $messages); + } + } + + /** + * {@inheritdoc} + */ + public function addCatalogue(MessageCatalogueInterface $catalogue) + { + foreach ($catalogue->getMessages() as $domain => $messages) { + $this->addMessages($messages, $domain); + } + + foreach ($catalogue->getResources() as $resource) { + $this->addResource($resource); + } + } + + /** + * {@inheritdoc} + */ + public function getResources() + { + return array_unique($this->resources); + } + + /** + * {@inheritdoc} + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + } +} diff --git a/src/Symfony/Component/Translation/MessageCatalogueInterface.php b/src/Symfony/Component/Translation/MessageCatalogueInterface.php new file mode 100644 index 0000000000..983e0b38f2 --- /dev/null +++ b/src/Symfony/Component/Translation/MessageCatalogueInterface.php @@ -0,0 +1,113 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * MessageCatalogueInterface. + * + * @author Fabien Potencier + */ +interface MessageCatalogueInterface +{ + /** + * Gets the catalogue locale. + * + * @return string The locale + */ + function getLocale(); + + /** + * Gets the domains. + * + * @param array An array of domains + */ + function getDomains(); + + /** + * Gets the messages within a given domain. + * + * If $domain is null, it returns all messages. + * + * @param string $domain The domain name + * + * @param array An array of messages + */ + function getMessages($domain = null); + + /** + * Sets a message translation. + * + * @param string $id The message id + * @param string $translation The messages translation + * @param string $domain The domain name + */ + function setMessage($id, $translation, $domain = 'messages'); + + /** + * Checks if a message has a translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return Boolean true if the message has a translation, false otherwise + */ + function hasMessage($id, $domain = 'messages'); + + /** + * Gets a message translation. + * + * @param string $id The message id + * @param string $domain The domain name + * + * @return string The message translation + */ + function getMessage($id, $domain = 'messages'); + + /** + * Sets translations for a given domain. + * + * @param string $messages An array of translations + * @param string $domain The domain name + */ + function setMessages($messages, $domain = 'messages'); + + /** + * Adds translations for a given domain. + * + * @param string $messages An array of translations + * @param string $domain The domain name + */ + function addMessages($messages, $domain = 'messages'); + + /** + * Merges translations from the given Catalogue into the current one. + * + * @param MessageCatalogueInterface $catalogue A MessageCatalogueInterface instance + */ + function addCatalogue(MessageCatalogueInterface $catalogue); + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + */ + function getResources(); + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource A resource instance + */ + function addResource(ResourceInterface $resource); +} diff --git a/src/Symfony/Component/Translation/PluralizationRules.php b/src/Symfony/Component/Translation/PluralizationRules.php new file mode 100644 index 0000000000..6fec6ef02e --- /dev/null +++ b/src/Symfony/Component/Translation/PluralizationRules.php @@ -0,0 +1,214 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Returns the plural rules for a given locale. + * + * @author Fabien Potencier + */ +class PluralizationRules +{ + static protected $rules = array(); + + /** + * Returns the plural position to use for the given locale and number. + * + * @param integer $number The number + * @param string $locale The locale + * + * @return integer The plural position + */ + static public function get($number, $locale) + { + if ($locale == "pt_BR") { + // temporary set a locale for brasilian + $locale = "xbr"; + } + + if (strlen($locale) > 3) { + $locale = substr($locale, 0, -strlen(strrchr($locale, '_'))); + } + + if (isset(self::$rules[$locale])) { + $return = call_user_func(self::$rules[$locale], $number); + + if (!is_int($return) || $return < 0) { + return 0; + } + + return $return; + } + + /* + * The plural rules are derived from code of the Zend Framework (2010-09-25), + * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + switch ($locale) { + case 'bo': + case 'dz': + case 'id': + case 'ja': + case 'jv': + case 'ka': + case 'km': + case 'kn': + case 'ko': + case 'ms': + case 'th': + case 'tr': + case 'vi': + case 'zh': + return 0; + break; + + case 'af': + case 'az': + case 'bn': + case 'bg': + case 'ca': + case 'da': + case 'de': + case 'el': + case 'en': + case 'eo': + case 'es': + case 'et': + case 'eu': + case 'fa': + case 'fi': + case 'fo': + case 'fur': + case 'fy': + case 'gl': + case 'gu': + case 'ha': + case 'he': + case 'hu': + case 'is': + case 'it': + case 'ku': + case 'lb': + case 'ml': + case 'mn': + case 'mr': + case 'nah': + case 'nb': + case 'ne': + case 'nl': + case 'nn': + case 'no': + case 'om': + case 'or': + case 'pa': + case 'pap': + case 'ps': + case 'pt': + case 'so': + case 'sq': + case 'sv': + case 'sw': + case 'ta': + case 'te': + case 'tk': + case 'ur': + case 'zu': + return ($number == 1) ? 0 : 1; + + case 'am': + case 'bh': + case 'fil': + case 'fr': + case 'gun': + case 'hi': + case 'ln': + case 'mg': + case 'nso': + case 'xbr': + case 'ti': + case 'wa': + return (($number == 0) || ($number == 1)) ? 0 : 1; + + case 'be': + case 'bs': + case 'hr': + case 'ru': + case 'sr': + case 'uk': + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'cs': + case 'sk': + return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2); + + case 'ga': + return ($number == 1) ? 0 : (($number == 2) ? 1 : 2); + + case 'lt': + return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2); + + case 'sl': + return ($number % 100 == 1) ? 0 : (($number % 100 == 2) ? 1 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3)); + + case 'mk': + return ($number % 10 == 1) ? 0 : 1; + + case 'mt': + return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)); + + case 'lv': + return ($number == 0) ? 0 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2); + + case 'pl': + return ($number == 1) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2); + + case 'cy': + return ($number == 1) ? 0 : (($number == 2) ? 1 : ((($number == 8) || ($number == 11)) ? 2 : 3)); + + case 'ro': + return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2); + + case 'ar': + return ($number == 0) ? 0 : (($number == 1) ? 1 : (($number == 2) ? 2 : ((($number >= 3) && ($number <= 10)) ? 3 : ((($number >= 11) && ($number <= 99)) ? 4 : 5)))); + + default: + return 0; + } + } + + /** + * Overrides the default plural rule for a given locale. + * + * @param string $rule A PHP callable + * @param string $locale The locale + * + * @return null + */ + static public function set($rule, $locale) + { + if ($locale == "pt_BR") { + // temporary set a locale for brasilian + $locale = "xbr"; + } + + if (strlen($locale) > 3) { + $locale = substr($locale, 0, -strlen(strrchr($locale, '_'))); + } + + if (!is_callable($rule)) { + throw new Exception('The given rule can not be called'); + } + + self::$rules[$locale] = $rule; + } +} diff --git a/src/Symfony/Component/Translation/Range.php b/src/Symfony/Component/Translation/Range.php new file mode 100644 index 0000000000..d16c762faa --- /dev/null +++ b/src/Symfony/Component/Translation/Range.php @@ -0,0 +1,102 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Range tests if a given number belongs to a given range. + * + * A range can represent a finite set of numbers: + * + * {1,2,3,4} + * + * A range can represent numbers between two numbers: + * + * [1, +Inf] + * ]-1,2[ + * + * The left delimiter can be [ (inclusive) or ] (exclusive). + * The right delimiter can be [ (exclusive) or ] (inclusive). + * Beside numbers, you can use -Inf and +Inf for the infinite. + * + * @author Fabien Potencier + */ +class Range +{ + /** + * Tests if the given number is in the range. + * + * @param integer $number A number + * @param string $range A range of numbers + */ + static public function test($number, $range) + { + $range = trim($range); + + if (!preg_match('/^'.self::getRangeRegexp().'$/x', $range, $matches)) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid range expression.', $range)); + } + + if ($matches[1]) { + foreach (explode(',', $matches[2]) as $n) { + if ($number == $n) { + return true; + } + } + } else { + $leftNumber = self::convertNumber($matches['left']); + $rightNumber = self::convertNumber($matches['right']); + + return + ('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber) + && + (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber) + ; + } + + return false; + } + + /** + * Returns a Regexp that matches valid ranges. + * + * @return string A Regexp (without the delimiters) + */ + static public function getRangeRegexp() + { + return <<[\[\]]) + \s* + (?-Inf|\-?\d+) + \s*,\s* + (?\+?Inf|\-?\d+) + \s* + (?[\[\]]) +EOF; + } + + static protected function convertNumber($number) + { + if ('-Inf' === $number) { + return log(0); + } elseif ('+Inf' === $number || 'Inf' === $number) { + return -log(0); + } else { + return (int) $number; + } + } +} diff --git a/src/Symfony/Component/Translation/Resource/FileResource.php b/src/Symfony/Component/Translation/Resource/FileResource.php new file mode 100644 index 0000000000..0e1f0b388d --- /dev/null +++ b/src/Symfony/Component/Translation/Resource/FileResource.php @@ -0,0 +1,68 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * FileResource represents a resource stored on the filesystem. + * + * @author Fabien Potencier + */ +class FileResource implements ResourceInterface +{ + protected $resource; + + /** + * Constructor. + * + * @param string $resource The file path to the resource + */ + public function __construct($resource) + { + $this->resource = realpath($resource); + } + + /** + * Returns a string representation of the Resource. + * + * @return string A string representation of the Resource + */ + public function __toString() + { + return (string) $this->resource; + } + + /** + * Returns the resource tied to this Resource. + * + * @return mixed The resource + */ + public function getResource() + { + return $this->resource; + } + + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param timestamp $timestamp The last time the resource was loaded + * + * @return Boolean true if the resource has not been updated, false otherwise + */ + public function isUptodate($timestamp) + { + if (!file_exists($this->resource)) { + return false; + } + + return filemtime($this->resource) < $timestamp; + } +} diff --git a/src/Symfony/Component/Translation/Resource/ResourceInterface.php b/src/Symfony/Component/Translation/Resource/ResourceInterface.php new file mode 100644 index 0000000000..21d79455b5 --- /dev/null +++ b/src/Symfony/Component/Translation/Resource/ResourceInterface.php @@ -0,0 +1,43 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * ResourceInterface is the interface that must be implemented by all Resource classes. + * + * @author Fabien Potencier + */ +interface ResourceInterface +{ + /** + * Returns a string representation of the Resource. + * + * @return string A string representation of the Resource + */ + function __toString(); + + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param int $timestamp The last time the resource was loaded + * + * @return Boolean true if the resource has not been updated, false otherwise + */ + function isUptodate($timestamp); + + /** + * Returns the resource tied to this Resource. + * + * @return mixed The resource + */ + function getResource(); +} diff --git a/src/Symfony/Component/Translation/Translator.php b/src/Symfony/Component/Translation/Translator.php new file mode 100644 index 0000000000..583a949d67 --- /dev/null +++ b/src/Symfony/Component/Translation/Translator.php @@ -0,0 +1,201 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Translator. + * + * @author Fabien Potencier + */ +class Translator implements TranslatorInterface +{ + protected $catalogues; + protected $locale; + protected $fallbackLocale; + protected $loaders; + protected $resources; + + /** + * Constructor. + * + * @param string $locale The locale + */ + public function __construct($locale = null) + { + $this->locale = $locale; + $this->loaders = array(); + $this->resources = array(); + $this->catalogues = array(); + } + + /** + * Adds a Loader. + * + * @param string $format The name of the loader (@see addResource()) + * @param LoaderInterface $loader A LoaderInterface instance + */ + public function addLoader($format, LoaderInterface $loader) + { + $this->loaders[$format] = $loader; + } + + /** + * Adds a Resource. + * + * @param string $format The name of the loader (@see addLoader()) + * @param mixed $resource The resource name + * @param string $locale The locale + * @param string $domain The domain + */ + public function addResource($format, $resource, $locale, $domain = 'messages') + { + if (!isset($this->resources[$locale])) { + $this->resources[$locale] = array(); + } + + $this->resources[$locale][] = array($format, $resource, $domain); + } + + /** + * {@inheritdoc} + */ + public function setLocale($locale) + { + $this->locale = $locale; + } + + /** + * Sets the fallback locale. + * + * @param string $locale The fallback locale + */ + public function setFallbackLocale($locale) + { + if (null !== $this->fallbackLocale) { + // needed as the fallback locale is used to fill-in non-yet translated messages + $this->catalogues = array(); + } + + $this->fallbackLocale = $locale; + } + + /** + * {@inheritdoc} + */ + public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) + { + if (!isset($locale)) { + $locale = $this->locale; + } + + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + + return strtr($this->catalogues[$locale]->getMessage($id, $domain), $parameters); + } + + /** + * {@inheritdoc} + */ + public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + if (!isset($locale)) { + $locale = $this->locale; + } + + if (!isset($this->catalogues[$locale])) { + $this->loadCatalogue($locale); + } + + return strtr($this->chooseMessage($this->catalogues[$locale]->getMessage($id, $domain), (int) $number, $locale), $parameters); + } + + protected function chooseMessage($message, $number, $locale) + { + $parts = explode('|', $message); + $explicitRules = array(); + $standardRules = array(); + foreach ($parts as $part) { + $part = trim($part); + + if (preg_match('/^(?'.Range::getRangeRegexp().')\s+(?.+?)$/x', $part, $matches)) { + $explicitRules[$matches['range']] = $matches['message']; + } elseif (preg_match('/^\w+\: +(.+)$/', $part, $matches)) { + $standardRules[] = $matches[1]; + } else { + $standardRules[] = $part; + } + } + + // try to match an explicit rule, then fallback to the standard ones + foreach ($explicitRules as $range => $m) { + if (Range::test($number, $range)) { + return $m; + } + } + + $position = PluralizationRules::get($number, $locale); + if (!isset($standardRules[$position])) { + throw new \InvalidArgumentException('Unable to choose a translation.'); + } + + return $standardRules[$position]; + } + + protected function loadCatalogue($locale) + { + if (isset($this->catalogues[$locale])) { + return; + } + + $this->catalogues[$locale] = new MessageCatalogue($locale); + if (!isset($this->resources[$locale])) { + return; + } + + foreach ($this->resources[$locale] as $resource) { + if (!isset($this->loaders[$resource[0]])) { + throw new \RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0])); + } + $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2])); + } + + $this->optimizeCatalogue($locale); + } + + protected function optimizeCatalogue($locale) + { + if (strlen($locale) > 3) { + $fallback = substr($locale, 0, -strlen(strrchr($locale, '_'))); + } else { + $fallback = $this->fallbackLocale; + } + + if (!isset($this->catalogues[$fallback])) { + $this->loadCatalogue($fallback); + } + + foreach ($this->catalogues[$fallback]->getResources() as $resource) { + $this->catalogues[$locale]->addResource($resource); + } + foreach ($this->catalogues[$fallback]->getDomains() as $domain) { + foreach ($this->catalogues[$fallback]->getMessages($domain) as $id => $translation) { + if (false === $this->catalogues[$locale]->hasMessage($id, $domain)) { + $this->catalogues[$locale]->setMessage($id, $translation, $domain); + } + } + } + } +} diff --git a/src/Symfony/Component/Translation/TranslatorInterface.php b/src/Symfony/Component/Translation/TranslatorInterface.php new file mode 100644 index 0000000000..8aac2e3045 --- /dev/null +++ b/src/Symfony/Component/Translation/TranslatorInterface.php @@ -0,0 +1,52 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * TranslatorInterface. + * + * @author Fabien Potencier + */ +interface TranslatorInterface +{ + /** + * Translates the given message. + * + * @param string $id The message id + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + function trans($id, array $parameters = array(), $domain = null, $locale = null); + + /** + * Translates the given choice message by choosing a translation according to a number. + * + * @param string $id The message id + * @param integer $number The number to use to find the indice of the message + * @param array $parameters An array of parameters for the message + * @param string $domain The domain for the message + * @param string $locale The locale + * + * @return string The translated string + */ + function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null); + + /** + * Sets the current locale. + * + * @param string $locale The locale + */ + function setLocale($locale); +} diff --git a/tests/Symfony/Tests/Component/Translation/RangeTest.php b/tests/Symfony/Tests/Component/Translation/RangeTest.php new file mode 100644 index 0000000000..68a57abb2d --- /dev/null +++ b/tests/Symfony/Tests/Component/Translation/RangeTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Translation; + +use Symfony\Component\Translation\Range; + +class RangeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTests + */ + public function testTest($expected, $number, $range) + { + $this->assertEquals($expected, Range::test($number, $range)); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTestException() + { + Range::test(1, 'foobar'); + } + + public function getTests() + { + return array( + array(true, 3, '{1,2, 3 ,4}'), + array(false, 10, '{1,2, 3 ,4}'), + array(false, 3, '[1,2]'), + array(true, 1, '[1,2]'), + array(true, 2, '[1,2]'), + array(false, 1, ']1,2['), + array(false, 2, ']1,2['), + array(true, log(0), '[-Inf,2['), + array(true, -log(0), '[-2,+Inf]'), + ); + } +} diff --git a/tests/Symfony/Tests/Component/Translation/TranslatorTest.php b/tests/Symfony/Tests/Component/Translation/TranslatorTest.php new file mode 100644 index 0000000000..185626085e --- /dev/null +++ b/tests/Symfony/Tests/Component/Translation/TranslatorTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Translation; + +use Symfony\Component\Translation\Translator; +use Symfony\Component\Translation\Loader\ArrayLoader; + +class TranslatorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTransTests + */ + public function testTrans($expected, $id, $translation, $parameters, $locale, $domain) + { + $translator = new Translator(); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array($id => $translation), $locale, $domain); + + $this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale)); + } + + /** + * @dataProvider getTransChoiceTests + */ + public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain) + { + $translator = new Translator(); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array($id => $translation), $locale, $domain); + + $this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale)); + } + + public function getTransTests() + { + return array( + array('Symfony2 est super !', 'Symfony2 is great!', 'Symfony2 est super !', array(), 'fr', ''), + array('Symfony2 est awesome !', 'Symfony2 is %what%!', 'Symfony2 est %what% !', array('%what%' => 'awesome'), 'fr', ''), + ); + } + + public function getTransChoiceTests() + { + return array( + array('Il y a 0 pomme', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', '{0} There is no apples|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + + array('Il y a 0 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + + array('Il y a 0 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + + array('Il n\'y a aucune pomme', '{0} There is no apple|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''), + array('Il y a 1 pomme', '{0} There is no apple|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''), + array('Il y a 10 pommes', '{0} There is no apple|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''), + ); + } +}