diff --git a/.gitattributes b/.gitattributes index 4e795980d..6930ab13c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -7,3 +7,4 @@ /.travis.yml export-ignore /phpunit.xml.dist export-ignore /tests export-ignore +/docs export-ignore diff --git a/.gitignore b/.gitignore index 5de5676cf..2d345fb66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ phpunit.xml composer.lock vendor/ +docs/_build/ diff --git a/README.md b/README.md index 88e772c33..c29400e8f 100644 --- a/README.md +++ b/README.md @@ -15,360 +15,11 @@ or a Doctrine DBAL (+Cache) based backend. When you create an I18N route and you go on it with your browser, the locale will be updated. -## Installation +## Documentation -```bash -composer.phar require besimple/i18n-routing-bundle -``` - -```php -//app/AppKernel.php -public function registerBundles() -{ - $bundles = array( - //... - new BeSimple\I18nRoutingBundle\BeSimpleI18nRoutingBundle(), - ); -} -``` - -### Update your configuration - -```yaml -# app/config/config.yml -be_simple_i18n_routing: ~ -``` - -## Create your routing - -To define internationalized routes in XML or YAML, you need to import the -routing file by using the ``be_simple_i18n`` type: - -```yaml -my_yaml_i18n_routes: - resource: "@MyWebsiteBundle/Resources/config/routing/i18n.yml" - type: be_simple_i18n - prefix: - en: /website - fr: /site - de: /webseite -my_xml_i18n_routes: - resource: "@MyWebsiteBundle/Resources/config/routing/i18n.xml" - type: be_simple_i18n -``` - -You can optionally specify a prefix or translated prefixes as shown above. - -### Yaml routing file - -```yaml -homepage: - locales: { en: "/welcome", fr: "/bienvenue", de: "/willkommen" } - defaults: { _controller: MyWebsiteBundle:Frontend:index } -``` - -### XML routing file - -```xml - - - - - /welcome - /bienvenue - /willkommen - MyWebsiteBundle:Frontend:index - - -``` - -Note that the XML file uses a different namespace than when using the core -loader: ``http://besim.pl/schema/i18n_routing``. - -### PHP routing file - -```php -addCollection( - $generator->generateRoutes( - 'homepage', - array('en' => '/welcome', 'fr' => '/bienvenue', 'de' => '/willkommen'), - new Route('', array( - '_controller' => 'MyWebsiteBundle:Frontend:index' - )) - ) -); - -return $collection; -``` - -### Controller annotations - -Annotation loading is only supported for Symfony 2.5 and greater and needs to be enabled as followed. -```YAML -# app/config/config.yml -be_simple_i18n_routing: - annotations: true -``` - -```PHP -use BeSimple\I18nRoutingBundle\Routing\Annotation\I18nRoute; - -class NoPrefixController -{ - /** - * @I18nRoute({ "en": "/welcome", "fr": "/bienvenue", "de": "/willkommen" }, name="homepage") - */ - public function indexAction() { } -} -``` - -### You can insert classic route in your routing - -#### Yaml routing file - -```yaml -homepage: - locales: { en: "/en/", fr: "/fr/", de: "/de/" } - defaults: { _controller: HelloBundle:Frontend:homepage } - -welcome: - locales: { en: "/welcome/{name}", fr: "/bienvenue/{name}", de: "/willkommen/{name}" } - defaults: { _controller: MyWebsiteBundle:Frontend:welcome } -``` - -#### XML routing file - -```xml - - - - - - HelloBundle:Hello:index - - - /welcome/{name} - /bienvenue/{name} - /willkommen/{name} - MyWebsiteBundle:Frontend:index - - -``` - -#### PHP routing file - -```php -add('hello', new Route('/hello/{name}', array( - '_controller' => 'HelloBundle:Hello:index', -))); -$collection->addCollection( - $generator->generateRoutes( - 'homepage', - array('en' => '/welcome/{name}', 'fr' => '/bienvenue/{name}', 'de' => '/willkommen/{name}'), - new Route('', array( - '_controller' => 'MyWebsiteBundle:Frontend:index', - )) - ) -); - -return $collection; -``` - -### Advanced locale support - -By default this bundle allows any locale to be used and there is no check if a locale is missing for a specific route. -This is great but sometimes you may wish to be strict, let take a look at the following configuration: -```YAML -be_simple_i18n_routing: - locales: - supported: ['en', 'nl'] - filter: true - strict: true -``` - -The `locales.supported` specifies which locales are supported. - -The `locales.filter` option is responsible for filtering out any unknown locales so only routes for 'en' and 'nl' are available. - -The `locales.strict` option when set to `true` is responsible for throwing a exception when a i18n route is found where the locale is unknown or where a locale is missing. -This option can also be set to `null` to disable locale is missing for a route exception and `false` to disable exceptions. - -### Route naming - -By default all routes that are imported are named '.' but sometimes you may want to change this behaviour. -To do this you can specify a route name inflector service in your configuration as followed. -```YAML -be_simple_i18n_routing: - route_name_inflector: 'my_route_name_inflector_service' -``` -*The service must implement the `BeSimple\I18nRoutingBundle\Routing\RouteGenerator\NameInflector\RouteNameInflectorInterface` interface.* - -There are currently 2 inflectors available by default [`be_simple_i18n_routing.route_name_inflector.postfix`](src/Routing/RouteGenerator/NameInflector/PostfixInflector.php) and [`be_simple_i18n_routing.route_name_inflector.default_postfix`](src/Routing/RouteGenerator/NameInflector/DefaultPostfixInflector.php). - -#### Default postfix inflector -The default postfix inflector changed the behaviour of to only add a locale postfix when the locale is not the default locale. -A example configuration is as followed. -```YAML -be_simple_i18n_routing: - route_name_inflector: 'my_route_name_inflector_service' - locales: - default_locale: '%kernel.default_locale%' -``` - -## Generate a route in your templates - -### Specify a locale - -#### Twig - - {{ path('homepage.en') }} - {{ path('homepage', { 'locale': 'en' }) }} - {{ path('homepage.fr') }} - {{ path('homepage', { 'locale': 'fr' }) }} - {{ path('homepage.de') }} - {{ path('homepage', { 'locale': 'de' }) }} - -#### PHP - -```php -generate('homepage.en') ?> -generate('homepage', array('locale' => 'en')) ?> -generate('homepage.fr') ?> -generate('homepage', array('locale' => 'fr')) ?> -generate('homepage.de') ?> -generate('homepage', array('locale' => 'de')) ?> -``` - -### Use current locale of user - -#### Twig - - {{ path('homepage') }} - -#### PHP - -```php -generate('homepage') ?> -``` - -## Translating the route attributes - -If the static parts of your routes are translated you get to the point really -fast when dynamic parts such as product slugs, category names or other dynamic -routing parameters should be translated. The bundle provides 2 implementations. - -After configuring the backend you want to use (see below for each one), you -can define a to be translated attribute in your route defaults: - -```yaml -product_view: - locales: { en: "/product/{slug}", de: "/produkt/{slug}" } - defaults: { _controller: "ShopBundle:Product:view", _translate: "slug" } -product_view2: - locales: { en: "/product/{category}/{slug}", de: "/produkt/{category}/{slug}" } - defaults: - _controller: "ShopBundle:Product:view" - _translate: ["slug", "category"] -``` - -The same goes with generating routes, now backwards: - - {{ path("product_view", {"slug": product.slug, "translate": "slug"}) }} - {{ path("product_view2", {"slug": product.slug, "translate": ["slug", "category]}) }} - -The reverse translation is only necessary if you have the "original" values -in your templates. If you have access to the localized value of the current -locale then you can just pass this and do not hint to translate it with the -"translate" key. - -### Doctrine DBAL Backend - -Configure the use of the DBAL backend - -```yaml -# app/config/config.yml -be_simple_i18n_routing: - attribute_translator: - type: doctrine_dbal - connection: default # Doctrine DBAL connection name. Using null (default value) will use the default connection - cache: apc -``` - -The Doctrine Backend has the following table structure: - -```sql -CREATE TABLE routing_translations ( - id INT NOT NULL, - route VARCHAR(255) NOT NULL, - locale VARCHAR(255) NOT NULL, - attribute VARCHAR(255) NOT NULL, - localized_value VARCHAR(255) NOT NULL, - original_value VARCHAR(255) NOT NULL, - UNIQUE INDEX UNIQ_291BA3522C420794180C698FA7AEFFB (route, locale, attribute), - INDEX IDX_291BA352D951F3E4 (localized_value), - PRIMARY KEY(id) -) ENGINE = InnoDB; -``` - -Lookups are made through the combination of route name, locale and attribute -of the route to be translated. - -Every lookup is cached in a Doctrine\Common\Cache\Cache instance that you -should configure to be APC, Memcache or Xcache for performance reasons. - -If you are using Doctrine it automatically registers a listener for SchemaTool -to create the routing_translations table for your database backend, you only -have to call: - - ./app/console doctrine:schema:update --dump-sql - ./app/console doctrine:schema:update --force - -### Translator backend - -This implementation uses the Symfony2 translator to translate the attributes. -The translation domain will be created using the pattern `_` - -```yaml -# app/config/config.yml -be_simple_i18n_routing: - attribute_translator: - type: translator -``` - -### Custom backend - -If you want to use a different implementation, simply create a service implementing -`BeSimple\I18nRoutingBundle\Routing\Translator\AttributeTranslatorInterface`. - -```yaml -# app/config/config.yml -be_simple_i18n_routing: - attribute_translator: - type: service - id: my_attribute_translator -``` +The documentation for this bundle is available in the `docs` directory of the bundle: +* Read the [BeSimpleI18nRoutingBundle documentation](http://symfony.com/doc/master/bundles/KnpMenuBundle/index.html) ## License diff --git a/composer.json b/composer.json index 211a69d42..e418ce3ec 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "besimple/i18n-routing-bundle", "type": "symfony-bundle", - "description": "Full routing internationalized on your Symfony2 project ", + "description": "Full routing internationalized on your Symfony2 project", "keywords": ["routing", "internationalisation", "Symfony2", "BeSimpleI18nRoutingBundle"], "homepage": "https://github.com/BeSimple/BeSimpleI18nRoutingBundle", "license": "MIT", diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..81576969a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,28 @@ +Documentation +==================== + +This directory contains documentation for BeSimple - I18n Routing Bundle Documentation, available on [**besimplei18nroutingbundle.readthedocs.io**](http://besimplei18nroutingbundle.readthedocs.io/). + +It is hosted by the great [readthedocs.org](http://readthedocs.org). + +Issues +------ + +The documentation uses [GitHub issues](https://github.com/BeSimple/BeSimpleI18nRoutingBundle/issues). + +Build +----- + +If you contribute to documentation you will want to check how it looks after your changes. +To be able to build the documentation, install [Sphinx](http://sphinx-doc.org/). + +1. [Install `pip`, Python package manager](https://pip.pypa.io/en/stable/installing/) +2. Download documentation requirements: `$ pip install -r requirements.txt` +3. Build the documentation:`$ sphinx-build -b html . build` + +Documentation index is `build/index.html`. + +Authors +------- + +See the list of [our amazing contributors](http://github.com/BeSimple/BeSimpleI18nRoutingBundle/contributors). diff --git a/docs/_theme/_exts/LICENSE b/docs/_theme/_exts/LICENSE new file mode 100644 index 000000000..bc6ad0497 --- /dev/null +++ b/docs/_theme/_exts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2015 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/docs/_theme/_exts/sensio/__init__.py b/docs/_theme/_exts/sensio/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/_theme/_exts/sensio/sphinx/__init__.py b/docs/_theme/_exts/sensio/sphinx/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/docs/_theme/_exts/sensio/sphinx/bestpractice.py b/docs/_theme/_exts/sensio/sphinx/bestpractice.py new file mode 100644 index 000000000..8297aff4d --- /dev/null +++ b/docs/_theme/_exts/sensio/sphinx/bestpractice.py @@ -0,0 +1,42 @@ +from docutils.parsers.rst import Directive, directives +from docutils import nodes +from string import upper +from sphinx.util.compat import make_admonition +from sphinx import addnodes +from sphinx.locale import _ + +class bestpractice(nodes.Admonition, nodes.Element): + pass + +class BestPractice(Directive): + has_content = True + required_arguments = 0 + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {} + + def run(self): + ret = make_admonition( + bestpractice, self.name, [_('Best Practice')], self.options, + self.content, self.lineno, self.content_offset, self.block_text, + self.state, self.state_machine) + if self.arguments: + argnodes, msgs = self.state.inline_text(self.arguments[0], + self.lineno) + para = nodes.paragraph() + para += argnodes + para += msgs + ret[0].insert(1, para) + + return ret + +def visit_bestpractice_node(self, node): + self.body.append(self.starttag(node, 'div', CLASS=('admonition best-practice'))) + self.set_first_last(node) + +def depart_bestpractice_node(self, node): + self.depart_admonition(node) + +def setup(app): + app.add_node(bestpractice, html=(visit_bestpractice_node, depart_bestpractice_node)) + app.add_directive('best-practice', BestPractice) diff --git a/docs/_theme/_exts/sensio/sphinx/codeblock.py b/docs/_theme/_exts/sensio/sphinx/codeblock.py new file mode 100644 index 000000000..0dc8e9dd7 --- /dev/null +++ b/docs/_theme/_exts/sensio/sphinx/codeblock.py @@ -0,0 +1,19 @@ +""" + :copyright: (c) 2010-2015 Fabien Potencier + :license: MIT, see LICENSE for more details. +""" + +from sphinx.directives.code import CodeBlock + +""" +A wrapper around the built-in CodeBlock class to always +enable line numbers. +""" +class NumberedCodeBlock(CodeBlock): + def run(self): + self.options['linenos'] = 'table' + return super(NumberedCodeBlock, self).run(); + +def setup(app): + app.add_directive('code-block', NumberedCodeBlock) + app.add_directive('sourcecode', NumberedCodeBlock) diff --git a/docs/_theme/_exts/sensio/sphinx/configurationblock.py b/docs/_theme/_exts/sensio/sphinx/configurationblock.py new file mode 100644 index 000000000..fc01ae29c --- /dev/null +++ b/docs/_theme/_exts/sensio/sphinx/configurationblock.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +""" + :copyright: (c) 2010-2012 Fabien Potencier + :license: MIT, see LICENSE for more details. +""" + +from docutils.parsers.rst import Directive, directives +from docutils import nodes + +class configurationblock(nodes.General, nodes.Element): + pass + +class ConfigurationBlock(Directive): + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + formats = { + 'html': 'HTML', + 'xml': 'XML', + 'php': 'PHP', + 'yaml': 'YAML', + 'jinja': 'Twig', + 'html+jinja': 'Twig', + 'jinja+html': 'Twig', + 'twig': 'Twig', + 'html+twig': 'Twig', + 'twig+html': 'Twig', + 'php+html': 'PHP', + 'html+php': 'PHP', + 'ini': 'INI', + 'php-annotations': 'Annotations', + 'php-standalone': 'Standalone Use', + 'php-symfony': 'Framework Use', + } + + def __init__(self, *args): + Directive.__init__(self, *args) + env = self.state.document.settings.env + config_block = env.app.config.config_block + + for language in config_block: + self.formats[language] = config_block[language] + + def run(self): + env = self.state.document.settings.env + + node = nodes.Element() + node.document = self.state.document + self.state.nested_parse(self.content, self.content_offset, node) + + entries = [] + for i, child in enumerate(node): + if isinstance(child, nodes.literal_block): + # add a title (the language name) before each block + #targetid = "configuration-block-%d" % env.new_serialno('configuration-block') + #targetnode = nodes.target('', '', ids=[targetid]) + #targetnode.append(child) + if 'language' in child: + language = child['language'] + else: + language = env.app.config.highlight_language + + innernode = nodes.emphasis(self.formats[language], self.formats[language]) + + para = nodes.paragraph() + para += [innernode, child] + + entry = nodes.list_item('') + entry.append(para) + entries.append(entry) + + resultnode = configurationblock() + resultnode.append(nodes.bullet_list('', *entries)) + + return [resultnode] + +def visit_configurationblock_html(self, node): + self.body.append(self.starttag(node, 'div', CLASS='configuration-block')) + +def depart_configurationblock_html(self, node): + self.body.append('\n') + +def visit_configurationblock_latex(self, node): + pass + +def depart_configurationblock_latex(self, node): + pass + +def setup(app): + app.add_node(configurationblock, + html=(visit_configurationblock_html, depart_configurationblock_html), + latex=(visit_configurationblock_latex, depart_configurationblock_latex)) + app.add_directive('configuration-block', ConfigurationBlock) + app.add_config_value('config_block', {}, 'env') diff --git a/docs/_theme/_exts/sensio/sphinx/php.py b/docs/_theme/_exts/sensio/sphinx/php.py new file mode 100644 index 000000000..9c84acc5c --- /dev/null +++ b/docs/_theme/_exts/sensio/sphinx/php.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +""" + :copyright: (c) 2010-2012 Fabien Potencier + :license: MIT, see LICENSE for more details. +""" + +from sphinx import addnodes +from sphinx.domains import Domain, ObjType +from sphinx.locale import l_, _ +from sphinx.directives import ObjectDescription +from sphinx.domains.python import py_paramlist_re as js_paramlist_re +from sphinx.roles import XRefRole +from sphinx.util.nodes import make_refnode +from sphinx.util.docfields import Field, GroupedField, TypedField + +def setup(app): + app.add_domain(PHPDomain) + +class PHPXRefRole(XRefRole): + def process_link(self, env, refnode, has_explicit_title, title, target): + # basically what sphinx.domains.python.PyXRefRole does + refnode['php:object'] = env.temp_data.get('php:object') + if not has_explicit_title: + title = title.lstrip('\\') + target = target.lstrip('~') + if title[0:1] == '~': + title = title[1:] + ns = title.rfind('\\') + if ns != -1: + title = title[ns+1:] + if target[0:1] == '\\': + target = target[1:] + refnode['refspecific'] = True + return title, target + +class PHPDomain(Domain): + """PHP language domain.""" + name = 'php' + label = 'PHP' + # if you add a new object type make sure to edit JSObject.get_index_string + object_types = { + } + directives = { + } + roles = { + 'func': PHPXRefRole(fix_parens=True), + 'class': PHPXRefRole(), + 'data': PHPXRefRole(), + 'attr': PHPXRefRole(), + } + initial_data = { + 'objects': {}, # fullname -> docname, objtype + } + + def clear_doc(self, docname): + for fullname, (fn, _) in self.data['objects'].items(): + if fn == docname: + del self.data['objects'][fullname] + + def find_obj(self, env, obj, name, typ, searchorder=0): + if name[-2:] == '()': + name = name[:-2] + objects = self.data['objects'] + newname = None + if searchorder == 1: + if obj and obj + '\\' + name in objects: + newname = obj + '\\' + name + else: + newname = name + else: + if name in objects: + newname = name + elif obj and obj + '\\' + name in objects: + newname = obj + '\\' + name + return newname, objects.get(newname) + + def resolve_xref(self, env, fromdocname, builder, typ, target, node, + contnode): + objectname = node.get('php:object') + searchorder = node.hasattr('refspecific') and 1 or 0 + name, obj = self.find_obj(env, objectname, target, typ, searchorder) + if not obj: + return None + return make_refnode(builder, fromdocname, obj[0], name, contnode, name) + + def get_objects(self): + for refname, (docname, type) in self.data['objects'].iteritems(): + yield refname, refname, type, docname, refname, 1 diff --git a/docs/_theme/_exts/sensio/sphinx/phpcode.py b/docs/_theme/_exts/sensio/sphinx/phpcode.py new file mode 100644 index 000000000..08102cf52 --- /dev/null +++ b/docs/_theme/_exts/sensio/sphinx/phpcode.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +""" + :copyright: (c) 2010-2012 Fabien Potencier + :license: MIT, see LICENSE for more details. +""" + +import re + +from docutils import nodes, utils + +from sphinx.util.nodes import split_explicit_title + +def php_namespace_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + env = inliner.document.settings.env + has_explicit_title, title, namespace = split_explicit_title(text) + + if len(re.findall(r'[^\\]\\[^\\]', rawtext)) > 0: + env.warn(env.docname, 'backslash not escaped in %s' % rawtext, lineno) + + full_url = build_url('namespace', namespace, None, None, inliner) + + if not has_explicit_title: + name = namespace.lstrip('\\') + ns = name.rfind('\\') + if ns != -1: + name = name[ns+1:] + title = name + list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=namespace)] + pnode = nodes.literal('', '', *list) + return [pnode], [] + +def php_class_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + env = inliner.document.settings.env + has_explicit_title, title, full_class = split_explicit_title(text) + backslash = full_class.rfind('\\') + namespace = full_class[:backslash] + class_name = full_class[backslash+1:] + + if len(re.findall(r'[^\\]\\[^\\]', rawtext)) > 0: + env.warn(env.docname, 'backslash not escaped in %s' % rawtext, lineno) + + full_url = build_url('class', namespace, class_name, None, inliner) + + if not has_explicit_title: + class_name = full_class.lstrip('\\') + ns = class_name.rfind('\\') + if ns != -1: + class_name = class_name[ns+1:] + title = class_name + list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class)] + pnode = nodes.literal('', '', *list) + return [pnode], [] + +def php_method_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + env = inliner.document.settings.env + has_explicit_title, title, class_and_method = split_explicit_title(text) + + ns = class_and_method.rfind('::') + full_class = class_and_method[:ns] + method = class_and_method[ns+2:] + backslash = full_class.rfind('\\') + namespace = full_class[:backslash] + class_name = full_class[backslash+1:] + + if len(re.findall(r'[^\\]\\[^\\]', rawtext)) > 0: + env.warn(env.docname, 'backslash not escaped in %s' % rawtext, lineno) + + full_url = build_url('method', namespace, class_name, method, inliner) + + if not has_explicit_title: + title = method + '()' + list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class + '::' + method + '()')] + pnode = nodes.literal('', '', *list) + return [pnode], [] + +def php_phpclass_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + has_explicit_title, title, full_class = split_explicit_title(text) + + full_url = 'http://php.net/manual/en/class.%s.php' % full_class.lower() + + if not has_explicit_title: + title = full_class + list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class)] + pnode = nodes.literal('', '', *list) + return [pnode], [] + +def php_phpmethod_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + has_explicit_title, title, class_and_method = split_explicit_title(text) + + ns = class_and_method.rfind('::') + full_class = class_and_method[:ns] + method = class_and_method[ns+2:] + + full_url = 'http://php.net/manual/en/%s.%s.php' % (full_class.lower(), method.lower()) + + if not has_explicit_title: + title = full_class + '::' + method + '()' + list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_class)] + pnode = nodes.literal('', '', *list) + return [pnode], [] + +def php_phpfunction_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + has_explicit_title, title, full_function = split_explicit_title(text) + + full_url = 'http://php.net/manual/en/function.%s.php' % full_function.replace('_', '-').lower() + + if not has_explicit_title: + title = full_function + list = [nodes.reference(title, title, internal=False, refuri=full_url, reftitle=full_function)] + pnode = nodes.literal('', '', *list) + return [pnode], [] + +def setup(app): + app.add_config_value('api_url', {}, 'env') + app.add_config_value('api_url_pattern', None, 'env') + app.add_config_value('namespace_separator', '/', 'env') + app.add_role('namespace', php_namespace_role) + app.add_role('class', php_class_role) + app.add_role('method', php_method_role) + app.add_role('phpclass', php_phpclass_role) + app.add_role('phpmethod', php_phpmethod_role) + app.add_role('phpfunction', php_phpfunction_role) + +def build_url(role, namespace, class_name, method, inliner): + env = inliner.document.settings.env + + if namespace is None: + namespace = '' + if class_name is None: + class_name = '' + if method is None: + method = '' + + if ('namespace_separator' in env.app.config): + namespace = namespace.replace('\\', env.app.config.namespace_separator) + else: + namespace = namespace.replace('\\', '/') + + if (env.app.config.api_url_pattern is None): + fqcn = '%(namespace)s{class}/%(class)s{/class}{method}/%(class)s{/method}' + api_url_pattern = env.app.config.api_url.replace('%s', fqcn) + api_url_pattern += '.html{method}#method_%(method)s{/method}' + else: + api_url_pattern = str(env.app.config.api_url_pattern) + + api_url_pattern = api_url_pattern.replace('{'+role+'}', '') + api_url_pattern = api_url_pattern.replace('{/'+role+'}', '') + + for unused_role in ('namespace', 'class', 'method'): + api_url_pattern = re.sub(r'{'+unused_role+'}.*?{/'+unused_role+'}', '', api_url_pattern) + + try: + full_url = api_url_pattern % {'namespace': namespace, 'class': class_name, 'method': method} + except (TypeError, ValueError): + env.warn(env.docname, 'unable to expand %s api_url with base ' + 'URL %r, please make sure the base contains \'%%s\' ' + 'exactly once' % (role, api_url_pattern)) + full_url = api_url_pattern + full_class + + return full_url diff --git a/docs/_theme/_exts/sensio/sphinx/refinclude.py b/docs/_theme/_exts/sensio/sphinx/refinclude.py new file mode 100644 index 000000000..0b1d72086 --- /dev/null +++ b/docs/_theme/_exts/sensio/sphinx/refinclude.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" + :copyright: (c) 2010-2012 Fabien Potencier + :license: MIT, see LICENSE for more details. +""" + +from docutils.parsers.rst import Directive, directives +from docutils import nodes + +class refinclude(nodes.General, nodes.Element): + pass + +class RefInclude(Directive): + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + document = self.state.document + + if not document.settings.file_insertion_enabled: + return [document.reporter.warning('File insertion disabled', + line=self.lineno)] + + env = self.state.document.settings.env + target = self.arguments[0] + + node = refinclude() + node['target'] = target + + return [node] + +def process_refinclude_nodes(app, doctree, docname): + env = app.env + for node in doctree.traverse(refinclude): + docname, labelid, sectname = env.domaindata['std']['labels'].get(node['target'], + ('','','')) + + if not docname: + return [document.reporter.error('Unknown target name: "%s"' % node['target'], + line=self.lineno)] + + resultnode = None + dt = env.get_doctree(docname) + for n in dt.traverse(nodes.section): + if labelid in n['ids']: + node.replace_self([n]) + break + +def setup(app): + app.add_node(refinclude) + app.add_directive('include-ref', RefInclude) + app.connect('doctree-resolved', process_refinclude_nodes) diff --git a/docs/_theme/_exts/setup.py b/docs/_theme/_exts/setup.py new file mode 100644 index 000000000..49582d108 --- /dev/null +++ b/docs/_theme/_exts/setup.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +from setuptools import setup, find_packages + +setup( + name = 'sphinx-php', + version = '1.0', + author = 'Fabien Potencier', + author_email = 'fabien@symfony.com', + description = 'Sphinx Extensions for PHP and Symfony', + license = 'MIT', + packages = find_packages(), + install_requires = ['Sphinx>=0.6'], +) diff --git a/docs/_theme/_static/style.css b/docs/_theme/_static/style.css new file mode 100644 index 000000000..361bfcaad --- /dev/null +++ b/docs/_theme/_static/style.css @@ -0,0 +1,75 @@ +.clearfix:before, .clearfix:after { + content: " "; + display: table +} + +.clearfix:after { + clear: both +} + +div.configuration-block { + margin: 1em 0; + position: relative +} + +div.configuration-block ul.simple { + margin: 0 +} + +div.configuration-block .simple li { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + background-color: #f2f2f2; + float: left; + list-style: none; + margin: 0 7px 0 0; + padding: 0 +} + +div.configuration-block li em { + font-size: 90%; + font-style: normal +} + +div.configuration-block .simple li a { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + color: #000000; + display: block; + padding: 5px 12px 3px; + text-decoration: none +} + +div.configuration-block .simple li.selected, +div.configuration-block .simple li a:hover { + background-color: #c0bec3; + color: #000000; +} +div.configuration-block .selected a { + color: #000000; + text-decoration: none +} + +div.configuration-block div.highlight pre { + margin: 0 +} + +div.configuration-block div { + left: 0; + position: absolute +} + +div.configuration-block div div { + position: static +} + +div.configuration-block .literal-block { + margin: 0 0 10px +} + +@supports (overflow:-webkit-marquee) and (justify-content:inherit) { + div.configuration-block div.highlight pre { + margin: 0 1em 0 0; + padding: 1em 5em 1em 1em + } +} diff --git a/docs/_theme/_static/style.js b/docs/_theme/_static/style.js new file mode 100644 index 000000000..279403170 --- /dev/null +++ b/docs/_theme/_static/style.js @@ -0,0 +1,23 @@ + +$(document).ready(function () { + var blocks = $("div.configuration-block"); + + blocks.find("[class^=highlight-]").hide(); + blocks.addClass("jsactive clearfix"); + blocks.each(function () { + var i = $("[class^=highlight-]:first", $(this)); + i.show(); + i.parents("ul:eq(0)").height(i.height() + 40); + }); + blocks.find("li").each(function () { + var i = $(":first", $(this)).html(); + $(":first ", $(this)).html(""), $(":first ", $(this)).append('' + i + ""), $(":first", $(this)).bind("click", function () { + $("[class^=highlight-]", $(this).parents("ul")).hide(), $("li", $(this).parents("ul")).removeClass("selected"), $(this).parent().addClass("selected"); + var i = $("[class^=highlight-]", $(this).parent("li")); + return i.show(), i.parents("ul:eq(0)").height(i.height() + 40), !1 + }) + }); + blocks.each(function () { + $("li:first", $(this)).addClass("selected") + }) +}); diff --git a/docs/_theme/_templates/layout.html b/docs/_theme/_templates/layout.html new file mode 100644 index 000000000..80c82837b --- /dev/null +++ b/docs/_theme/_templates/layout.html @@ -0,0 +1,4 @@ +{% extends "!layout.html" %} + +{% set css_files = css_files + ['_static/style.css'] %} +{% set script_files = script_files + ["_static/style.js"] %} diff --git a/docs/attribute_translation.rst b/docs/attribute_translation.rst new file mode 100644 index 000000000..de96f8d15 --- /dev/null +++ b/docs/attribute_translation.rst @@ -0,0 +1,105 @@ +Route Attribute Translation +=========================== + +If the static parts of your routes are translated you get to the point really +fast when dynamic parts such as product slugs, category names or other dynamic +routing parameters should be translated. The bundle provides 2 implementations. + +After configuring the backend you want to use (see below for each one), you +can define a to be translated attribute in your route defaults: + +.. code-block:: yaml + + product_view: + locales: { en: "/product/{slug}", de: "/produkt/{slug}" } + defaults: { _controller: "ShopBundle:Product:view", _translate: "slug" } + + product_view2: + locales: { en: "/product/{category}/{slug}", de: "/produkt/{category}/{slug}" } + defaults: + _controller: "ShopBundle:Product:view" + _translate: ["slug", "category"] + +The same goes with generating routes, now backwards: + +.. code-block:: html+jinja + + {{ path("product_view", {"slug": product.slug, "translate": "slug"}) }} + {{ path("product_view2", {"slug": product.slug, "translate": ["slug", "category]}) }} + +The reverse translation is only necessary if you have the "original" values +in your templates. If you have access to the localized value of the current +locale then you can just pass this and do not hint to translate it with the +"translate" key. + +Doctrine DBAL Backend +--------------------- + +Configure the use of the DBAL backend + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + attribute_translator: + type: doctrine_dbal + connection: default # Doctrine DBAL connection name. Using null (default value) will use the default connection + cache: apc + +The Doctrine Backend has the following table structure: + +.. code-block:: sql + + CREATE TABLE routing_translations ( + id INT NOT NULL, + route VARCHAR(255) NOT NULL, + locale VARCHAR(255) NOT NULL, + attribute VARCHAR(255) NOT NULL, + localized_value VARCHAR(255) NOT NULL, + original_value VARCHAR(255) NOT NULL, + UNIQUE INDEX UNIQ_291BA3522C420794180C698FA7AEFFB (route, locale, attribute), + INDEX IDX_291BA352D951F3E4 (localized_value), + PRIMARY KEY(id) + ) ENGINE = InnoDB; + +Lookups are made through the combination of route name, locale and attribute +of the route to be translated. + +Every lookup is cached in a Doctrine\Common\Cache\Cache instance that you +should configure to be APC, Memcache or Xcache for performance reasons. + +If you are using Doctrine it automatically registers a listener for SchemaTool +to create the routing_translations table for your database backend, you only +have to call: + +.. code-block:: bash + + ./app/console doctrine:schema:update --dump-sql + ./app/console doctrine:schema:update --force + +Translator backend +------------------ + +This implementation uses the Symfony2 translator to translate the attributes. +The translation domain will be created using the pattern `_` + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + attribute_translator: + type: translator + +Custom backend +~~~~~~~~~~~~~~ + +If you want to use a different implementation, simply create a service implementing +`BeSimple\I18nRoutingBundle\Routing\Translator\AttributeTranslatorInterface`. + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + attribute_translator: + type: service + id: my_attribute_translator diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..9fb894c91 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,281 @@ +# -*- coding: utf-8 -*- +# +# BeSimpleI18nRoutingBundle documentation build configuration file, created by +# sphinx-quickstart on Sat Jul 28 21:58:57 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.append(os.path.abspath('_theme/_exts')) + +# adding PhpLexer +from sphinx.highlighting import lexers +from pygments.lexers.compiled import CLexer +from pygments.lexers.special import TextLexer +from pygments.lexers.text import RstLexer +from pygments.lexers.web import PhpLexer + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.todo', + 'sensio.sphinx.refinclude', + 'sensio.sphinx.configurationblock', + 'sensio.sphinx.phpcode', + 'sensio.sphinx.codeblock' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_theme/_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'BeSimple - I18n Routing Bundle Documentation' +copyright = '' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +# version = '2.2' +# The full version, including alpha/beta/rc tags. +# release = '2.2.13' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_theme'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# -- Settings for symfony doc extension --------------------------------------------------- + +# enable highlighting for PHP code not between ```` by default +lexers['markdown'] = TextLexer() +lexers['php'] = PhpLexer(startinline=True) +lexers['php-annotations'] = PhpLexer(startinline=True) +lexers['php-standalone'] = PhpLexer(startinline=True) +lexers['rst'] = RstLexer() + +config_block = { + 'markdown': 'Markdown', + 'rst': 'reStructuredText' +} + +# use PHP as the primary domain +primary_domain = 'php' + +# set url for API links +# api_url = 'http://api.symfony.com/master/%s' + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_theme/_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'BeSimpleI18nRoutingBundleDoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'BeSimpleI18nRoutingBundle.tex', u'BeSimple - I18n Routing Bundle Documentation', + u'BeSimple I18n Routing Bundle community', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'BeSimpleI18nRoutingBundle', u'BeSimple - I18n Routing Bundle Documentation', + [u'BeSimple - I18n Routing Bundle community'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'BeSimpleI18nRoutingBundle', u'BeSimple - I18n Routing Bundle Documentation', + u'BeSimple - I18n Routing Bundle community', 'Full routing internationalized on your Symfony2 project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# Use PHP syntax highlighting in code examples by default +highlight_language='php' + diff --git a/docs/customize_router.rst b/docs/customize_router.rst new file mode 100644 index 000000000..bff69b1e1 --- /dev/null +++ b/docs/customize_router.rst @@ -0,0 +1,69 @@ +Customizing the Router +====================== + +Route Naming +------------ + +By default all routes that are imported are named '.' but sometimes you may want to change this behaviour. +To do this you can specify a route name inflector service in your configuration as followed. + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + # Service must implement the `BeSimple\I18nRoutingBundle\Routing\RouteGenerator\NameInflector\RouteNameInflectorInterface` + route_name_inflector: "my_route_name_inflector_service" + +There are currently 2 inflectors available by default ``be_simple_i18n_routing.route_name_inflector.postfix`` and ``be_simple_i18n_routing.route_name_inflector.default_postfix``. + +Default Postfix Inflector +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The default postfix inflector changed the behaviour to only add a locale postfix when the locale is not the default locale. +For this to work correctly you must configure a ``default_locale``. + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + route_name_inflector: 'be_simple_i18n_routing.route_name_inflector.default_postfix' + locales: + default_locale: '%kernel.default_locale%' + + +Route Filtering +--------------- + +During the development of your system you may want to filter out a (not fully) implemented locale. +This can be done why configuring the supported locales and enabling filtering. + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + locales: + supported: ['en', 'nl'] + filter: true + +The above configuration will only generate routes for the ``en`` and ``nl`` locale and will ignore any other locales. + + +Strict Route Locales +-------------------- + +During development it can be nice to ensure that all routes are localized. +This can be done why configuring the supported locales and enabling strict localization. + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + locales: + supported: ['en', 'nl'] + strict: true + +The ``strict`` options has 3 settings: + +- ``true`` for throwing a exception when a i18n route is found where the locale is unknown or where a locale is missing. +- ``null`` for throwing a exception where a locale is missing. +- ``false`` for disabling strict routes (default) diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..82a1a947d --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,358 @@ +Using BeSimpleI18nRoutingBundle +=============================== + +Welcome to BeSimpleI18nRoutingBundle - generate I18N Routes simply and quickly + +.. toctree:: + :hidden: + + self + +Installation +------------ + +Step 1: Download the Bundle +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Open a command console, enter your project directory and execute the +following command to download the latest stable version of this bundle: + +.. code-block:: bash + + $ composer require besimple/i18n-routing-bundle "^3.0" + +This command requires you to have Composer installed globally, as explained +in the `installation chapter`_ of the Composer documentation. + +.. _`installation chapter`: https://getcomposer.org/doc/00-intro.md + +Step 2: Enable the Bundle +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Then, enable the bundle by adding the following line in the ``app/AppKernel.php`` +file of your project: + +.. code-block:: php + + // app/AppKernel.php + + // ... + class AppKernel extends Kernel + { + public function registerBundles() + { + $bundles = array( + // ... + + new BeSimple\I18nRoutingBundle\BeSimpleI18nRoutingBundle(), + ); + + // ... + } + + // ... + } + + +Step 3: (optional) Configure the bundle +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The bundle comes with a sensible default configuration, which is listed below. +If you skip this step, these defaults will be used. + +.. code-block:: yaml + + # app/config/config.yml + be_simple_i18n_routing: + # if true, enables the annotation route loader + annotations: true + + locales: + # the default locale, used when generating routes + default_locale: null + # the supported locales used by "filter" and "strict" + supported: [] + # if true, filter out any locales not in "supported" + filter: false + # if true, filter out any locales not in "supported" + strict: false + + attribute_translator: + # if true, enables the attribute translator + enabled: false + + # the type of attribute translator to use + type: translator + # When defining type as "service" then + # add the id parameter with the service id to use (e.g. id: "my_attribute_translator_service_id") + # and ensure the service implements "\BeSimple\I18nRoutingBundle\Routing\Translator\AttributeTranslatorInterface" + # + # When defining type as "doctrine_dbal" then + # optionally add the connection parameter to set the dbal connection name (e.g. connection: "connection_name") + # optionally add a caching configuration using the cache parameter: + # type: array | apc | xcache | memcache + # when the cache type is "memcache" then (optionally) add the connection information: + # type: memcache + # host: 127.0.0.1 + # port: 11211 + # instance_class: Memcache + # class: \Doctrine\Common\Cache\MemcacheCache + + # the route name inflector service + route_name_inflector: 'be_simple_i18n_routing.route_name_inflector.postfix' + + +Create your localized routes! +----------------------------- + +Importing Routes +~~~~~~~~~~~~~~~~ + +To define internationalized routes, you need to import the routing file using the ``be_simple_i18n`` type: + +.. code-block:: yaml + + # app/config/routing.yml + + my_yaml_i18n_routes: + type: be_simple_i18n + resource: "@AppBundle/Resources/config/routing/i18n.yml" + + my_xml_i18n_routes: + type: be_simple_i18n + resource: "@AppBundle/Resources/config/routing/i18n.xml" + + # For annotation support ensure that annotations is true in the configuration + my_annotation_i18n_routes: + resource: '@AppBundle/Controller/' + type: annotation + +Defining Routes +~~~~~~~~~~~~~~~ + +.. configuration-block:: + + .. code-block:: yaml + + # @AppBundle/Resources/config/routing/i18n.yml + homepage: + path: /blog/{slug} + defaults: { _controller: AppBundle:Frontend:index } + + .. code-block:: xml + + + + + + + /welcome + /bienvenue + /willkommen + AppBundle:Frontend:index + + + + .. code-block:: php + + // app/config/routing.php + use Symfony\Component\Routing\RouteCollection; + use BeSimple\I18nRoutingBundle\Routing\RouteGenerator\I18nRouteGenerator; + + $generator = new I18nRouteGenerator(); + + $collection = new RouteCollection(); + $collection->addCollection( + $generator->generateRoutes( + 'homepage', + array( + 'en' => '/welcome', + 'fr' => '/bienvenue', + 'de' => '/willkommen' + ), + new Route('', array( + '_controller' => 'AppBundle:Frontend:index' + )) + ) + ); + + return $collection; + + .. code-block:: php-annotations + + // @AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use BeSimple\I18nRoutingBundle\Routing\Annotation\I18nRoute; + + class FrontendController extends Controller + { + /** + * @I18nRoute({ "en": "/welcome/{name}", "fr": "/bienvenue/{name}", "de": "/willkommen/{name}" }, name="homepage") + */ + public function indexAction($name) + { + // ... + } + } + +Using Normal Routes +~~~~~~~~~~~~~~~~~~~ + +Sometimes you have routes that don't need to be translated. +To allow this simply add the routes as followed. + + +.. configuration-block:: + + .. code-block:: yaml + + # @AppBundle/Resources/config/routing/i18n.yml + homepage: + path: "/{name}" + defaults: { _controller: AppBundle:Hello:index } + + welcome: + locales: { en: "/welcome/{name}", fr: "/bienvenue/{name}", de: "/willkommen/{name}" } + defaults: { _controller: AppBundle:Frontend:welcome } + + .. code-block:: xml + + + + + + + AppBundle:Hello:index + + + /welcome/{name} + /bienvenue/{name} + /willkommen/{name} + AppBundle:Frontend:index + + + + .. code-block:: php + + // app/config/routing.php + use BeSimple\I18nRoutingBundle\Routing\RouteGenerator\I18nRouteGenerator; + use Symfony\Component\Routing\RouteCollection; + use Symfony\Component\Routing\Route; + + $generator = new I18nRouteGenerator(); + + $collection = new RouteCollection(); + $collection->add('hello', new Route('/hello/{name}', array( + '_controller' => 'AppBundle:Hello:index', + ))); + $collection->addCollection( + $generator->generateRoutes( + 'homepage', + array('en' => '/welcome/{name}', 'fr' => '/bienvenue/{name}', 'de' => '/willkommen/{name}'), + new Route('', array( + '_controller' => 'AppBundle:Frontend:index', + )) + ) + ); + + return $collection; + + .. code-block:: php-annotations + + // @AppBundle/Controller/BlogController.php + namespace AppBundle\Controller; + + use Symfony\Bundle\FrameworkBundle\Controller\Controller; + use BeSimple\I18nRoutingBundle\Routing\Annotation\I18nRoute; + + class FrontendController extends Controller + { + /** + * @I18nRoute("/{name}", name="hello") + */ + public function helloAction($slug) + { + // ... + } + + /** + * @I18nRoute({ "en": "/welcome/{name}", "fr": "/bienvenue/{name}", "de": "/willkommen/{name}" }, name="homepage") + */ + public function indexAction($name) + { + // ... + } + } + +Prefixing Imported Routes +~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can also choose to provide a "prefix" for the imported routes. +Combining this with normal routes will automatically localize them. + + +.. configuration-block:: + + .. code-block:: yaml + + # app/config/routing.yml + app: + resource: '@AppBundle/Controller/' + type: be_simple_i18n + prefix: + en: /website + fr: /site + de: /webseite + + .. code-block:: xml + + + + + + + /english + /german + /french + + + + .. code-block:: php + + // app/config/routing.php + use BeSimple\I18nRoutingBundle\Routing\RouteGenerator\I18nRouteGenerator; + use Symfony\Component\Routing\RouteCollection; + + $generator = new I18nRouteGenerator(); + + $app = $loader->import('@AppBundle/Controller/', 'annotation'); + $app = $generator->generateCollection(array( + 'en' => '/english', + 'de' => '/german', + 'fr' => '/french', + ), $app); + + $collection = new RouteCollection(); + $collection->addCollection($app); + + return $collection; + + +More Stuff +---------- + +.. toctree:: + :maxdepth: 1 + + route_generation + attribute_translation + customize_router diff --git a/docs/route_generation.rst b/docs/route_generation.rst new file mode 100644 index 000000000..be9fdac4e --- /dev/null +++ b/docs/route_generation.rst @@ -0,0 +1,46 @@ +Route Generation +================ + +Using a specify locale +---------------------- + +.. configuration-block:: + + .. code-block:: html+jinja + + {{ path('homepage.en') }} + {{ path('homepage', { 'locale': 'en' }) }} + + {{ path('homepage.fr') }} + {{ path('homepage', { 'locale': 'fr' }) }} + + {{ path('homepage.de') }} + {{ path('homepage', { 'locale': 'de' }) }} + + .. code-block:: html+php + + generate('homepage.en') ?> + generate('homepage', array('locale' => 'en')) ?> + generate('homepage.fr') ?> + generate('homepage', array('locale' => 'fr')) ?> + generate('homepage.de') ?> + generate('homepage', array('locale' => 'de')) ?> + + +.. note:: + + When using the locale to generate the route make sure you use the ``locale`` parameter and not ``_locale``. + + +Using the current locale +------------------------ + +.. configuration-block:: + + .. code-block:: html+jinja + + {{ path('homepage') }} + + .. code-block:: html+php + + generate('homepage') ?>