Skip to content
This repository was archived by the owner on Sep 16, 2021. It is now read-only.

Commit 1a466dd

Browse files
dbuwouterj
authored andcommitted
clean up cookbook articles
1 parent bd1d4ee commit 1a466dd

21 files changed

+722
-735
lines changed

book/handling_multilang.rst

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
.. index::
2+
single: Multi Language; Book
3+
4+
Handling Multi-Language Documents
5+
=================================
6+
7+
The default storage layer of the CMF, PHPCR-ODM, can handle translations
8+
transparently.
9+
10+
.. tip::
11+
12+
This chapter assumes you already have an idea how to interact with
13+
PHPCR-ODM, which was introduced in the :doc:`Database chapter <database_layer>`.
14+
15+
.. caution::
16+
17+
You also need the ``intl`` php extension installed and enabled (otherwise
18+
composer will tell you it can't find ext-intl). If you get issues that some
19+
locales can not be loaded, have a look at `this discussion about ICU`_.
20+
21+
Initial Language Choice: Lunetics LocaleBundle
22+
----------------------------------------------
23+
24+
The CMF recommends to rely on the `LuneticsLocaleBundle`_
25+
to handle requests to ``/`` on your website. This bundle provides the tools
26+
to select the best locale for the user based on various criteria.
27+
28+
When you configure ``lunetics_locale``, it is recommended to use a parameter
29+
for the locales, as you need to configure the locales for other bundles
30+
(e.g. the CoreBundle) too.
31+
32+
.. configuration-block::
33+
34+
.. code-block:: yaml
35+
36+
lunetics_locale:
37+
allowed_locales: "%locales%"
38+
39+
.. code-block:: xml
40+
41+
<?xml version="1.0" charset="UTF-8" ?>
42+
<container xmlns="http://symfony.com/schema/dic/services">
43+
44+
<config xmlns="http://example.org/schema/dic/lunetics_locale">
45+
<allowed-locales>%locales%</allowed-locales>
46+
</config>
47+
</container>
48+
49+
.. code-block:: php
50+
51+
$container->loadFromExtension('lunetics_locale', array(
52+
'allowed_locales' => '%locales%',
53+
));
54+
55+
Configuring Available Locales
56+
-----------------------------
57+
58+
The CoreBundle needs to be configure with the available locales. If it is
59+
not configured with locales, it registeres a listener that removes all
60+
translation mapping from PHPCR-ODM documents.
61+
62+
.. configuration-block::
63+
64+
.. code-block:: yaml
65+
66+
cmf_core:
67+
multilang:
68+
locales: "%locales%"
69+
70+
.. code-block:: xml
71+
72+
<?xml version="1.0" charset="UTF-8" ?>
73+
<container xmlns="http://symfony.com/schema/dic/services">
74+
75+
<config xmlns="http://cmf.symfony.com/schema/dic/core">
76+
<multilang>%locales%</multilang>
77+
</config>
78+
</container>
79+
80+
.. code-block:: php
81+
82+
$container->loadFromExtension('cmf_core', array(
83+
'multilang' => array(
84+
'locales' => '%locales%',
85+
),
86+
));
87+
88+
PHPCR-ODM multi-language Documents
89+
----------------------------------
90+
91+
You can mark any properties as being translatable and have the document
92+
manager store and load the correct language for you. Note that translation
93+
always happens on a document level, not on the individual translatable fields.
94+
95+
.. code-block:: php
96+
97+
<?php
98+
99+
// src/Acme/DemoBundle/Document/MyPersistentClass.php
100+
101+
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCR;
102+
103+
/**
104+
* @PHPCR\Document(translator="attribute")
105+
*/
106+
class MyPersistentClass
107+
{
108+
/**
109+
* Translated property
110+
* @PHPCR\String(translated=true)
111+
*/
112+
private $topic;
113+
114+
// ...
115+
}
116+
117+
.. seealso::
118+
119+
Read more about multi-language documents in the
120+
`PHPCR-ODM documentation on multi-language`_ and see
121+
:doc:`../bundles/phpcr_odm/multilang` for information how to configure
122+
PHPCR-ODM correctly.
123+
124+
The default documents provided by the CMF bundles are translated documents.
125+
The CoreBundle removes the translation mapping if ``multilang`` is not
126+
configured.
127+
128+
The routes are handled differently, as you can read in the next section.
129+
130+
Routing
131+
-------
132+
133+
The ``DynamicRouter`` uses a route source to get routes that could match a
134+
request. The concept of the default PHPCR-ODM source is to map the request URL
135+
onto an id, which in PHPCR terms is the repository path to a node. This allows
136+
for a very efficient lookup without needing a full search over the repository.
137+
But a PHPCR node has exactly one path, therefore you need a separate route
138+
document per locale. The cool thing with this is that you can localize
139+
the URL for each language. Simply create one route document per locale.
140+
141+
As all routes point to the same content, the route generator can handle
142+
picking the correct route for you when you generate the route from the
143+
content. See also
144+
":ref:`ContentAwareGenerator and Locales <component-route-generator-and-locales>`".
145+
146+
.. _book_handling-multilang_sonata-admin:
147+
148+
Sonata PHPCR-ODM Admin
149+
----------------------
150+
151+
.. note::
152+
153+
Using sonata admin is one way to make your content editable. A book
154+
chapter on sonata admin is planned. Meanwhile, read
155+
:doc:`Sonata Admin <../cookbook/creating_a_cms/sonata-admin>` in the
156+
"Creating a CMS" tutorial.
157+
158+
The first step is to configure sonata admin. You should place the
159+
LuneticsLocaleBundle language switcher in the ``topnav`` bar. To do this,
160+
configure the template for the ``user_block``:
161+
162+
.. configuration-block::
163+
164+
.. code-block:: yaml
165+
166+
# app/config/config.yml
167+
sonata_admin:
168+
# ...
169+
templates:
170+
user_block: AcmeCoreBundle:Admin:admin_topnav.html.twig
171+
172+
.. code-block:: xml
173+
174+
<!-- app/config/config.xml -->
175+
<?xml version="1.0" encoding="UTF-8" ?>
176+
<container xmlns="http://symfony.com/schema/dic/services">
177+
<config xmlns="http://sonata-project.org/schema/dic/admin">
178+
<template user-block="AcmeCoreBundle:Admin:admin_topnav.html.twig"/>
179+
</config>
180+
</container>
181+
182+
183+
.. code-block:: php
184+
185+
// app/config/config.php
186+
$container->loadFromExtension('sonata_admin', array(
187+
'templates' => array(
188+
'user_block' => 'AcmeCoreBundle:Admin:admin_topnav.html.twig',
189+
),
190+
));
191+
192+
And the template looks like this:
193+
194+
.. code-block:: jinja
195+
196+
{# src/Acme/CoreBundle/Resources/views/Admin/admin_topnav.html.twig #}
197+
{% extends 'SonataAdminBundle:Core:user_block.html.twig' %}
198+
199+
{% block user_block %}
200+
{{ locale_switcher(null, null, 'AcmeCoreBundle:Admin:switcher_links.html.twig') }}
201+
{{ parent() }}
202+
{% endblock %}
203+
204+
You need to tell the ``locale_switcher`` to use a custom template to display
205+
the links, which looks like this:
206+
207+
.. code-block:: jinja
208+
209+
{# src/Acme/CoreBundle/Resources/views/Admin/switcher_links.html.twig #}
210+
Switch to :
211+
{% for locale in locales %}
212+
{% if loop.index > 1 %} | {% endif %}<a href="{{ locale.link }}" title="{{ locale.locale_target_language }}">{{ locale.locale_target_language }}</a>
213+
{% endfor %}
214+
215+
Now what is left to do is to update the sonata routes to become locale aware:
216+
217+
.. configuration-block::
218+
219+
.. code-block:: yaml
220+
221+
# app/config/routing.yml
222+
223+
admin_dashboard:
224+
pattern: /{_locale}/admin/
225+
defaults:
226+
_controller: FrameworkBundle:Redirect:redirect
227+
route: sonata_admin_dashboard
228+
permanent: true # this for 301
229+
230+
admin:
231+
resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
232+
prefix: /{_locale}/admin
233+
234+
sonata_admin:
235+
resource: .
236+
type: sonata_admin
237+
prefix: /{_locale}/admin
238+
239+
# redirect routes for the non-locale routes
240+
admin_without_locale:
241+
pattern: /admin
242+
defaults:
243+
_controller: FrameworkBundle:Redirect:redirect
244+
route: sonata_admin_dashboard
245+
permanent: true # this for 301
246+
247+
admin_dashboard_without_locale:
248+
pattern: /admin/dashboard
249+
defaults:
250+
_controller: FrameworkBundle:Redirect:redirect
251+
route: sonata_admin_dashboard
252+
permanent: true
253+
254+
.. code-block:: xml
255+
256+
<?xml version="1.0" encoding="UTF-8" ?>
257+
<routes xmlns="http://symfony.com/schema/dic/routing">
258+
259+
<route id="admin_dashboard" pattern="/{_locale}/admin/">
260+
<default key="_controller">FrameworkBundle:Redirect:redirect</default>
261+
<default key="route">sonata_admin_dashboard</default>
262+
<default "permanent">true</default>
263+
</route>
264+
265+
<import resource="@SonataAdminBundle/Resources/config/routing/sonata_admin.xml"
266+
prefix="/{_locale}/admin"
267+
/>
268+
269+
<import resource="." type="sonata_admin" prefix="/{_locale}/admin"/>
270+
271+
<!-- redirect routes for the non-locale routes -->
272+
<route id="admin_without_locale" pattern="/admin">
273+
<default key="_controller">FrameworkBundle:Redirect:redirect</default>
274+
<default key="route">sonata_admin_dashboard</default>
275+
<default "permanent">true</default>
276+
</route>
277+
<route id="admin_dashboard_without_locale" pattern="/admin/dashboard">
278+
<default key="_controller">FrameworkBundle:Redirect:redirect</default>
279+
<default key="route">sonata_admin_dashboard</default>
280+
<default "permanent">true</default>
281+
</route>
282+
</routes>
283+
284+
.. code-block:: php
285+
286+
// app/config/routing.php
287+
288+
$collection = new RouteCollection();
289+
290+
$collection->add('admin_dashboard', new Route('/{_locale}/admin/', array(
291+
'_controller' => 'FrameworkBundle:Redirect:redirect',
292+
'route' => 'sonata_admin_dashboard',
293+
'permanent' => true,
294+
)));
295+
296+
$sonata = $loader->import('@SonataAdminBundle/Resources/config/routing/sonata_admin.xml');
297+
$sonata->addPrefix('/{_locale}/admin');
298+
$collection->addCollection($sonata);
299+
300+
$sonata = $loader->import('.', 'sonata_admin');
301+
$sonata->addPrefix('/{_locale}/admin');
302+
$collection->addCollection($sonata);
303+
304+
$collection->add('admin_without_locale', new Route('/admin', array(
305+
'_controller' => 'FrameworkBundle:Redirect:redirect',
306+
'route' => 'sonata_admin_dashboard',
307+
'permanent' => true,
308+
)));
309+
310+
$collection->add('admin_dashboard_without_locale', new Route('/admin/dashboard', array(
311+
'_controller' => 'FrameworkBundle:Redirect:redirect',
312+
'route' => 'sonata_admin_dashboard',
313+
'permanent' => true,
314+
)));
315+
316+
return $collection
317+
318+
Now reload the admin dashboard. The URL should be prefixed with the
319+
default locale, for example ``/de/admin/dashboard``. When clicking on the
320+
language switcher, the page reloads and displays the correct content for the
321+
requested language.
322+
323+
If your documents implement the TranslatableInterface, you can
324+
:ref:`configure the translatable admin extension <bundle-core-translatable-admin-extension>`
325+
to get a language choice field to let the administrator
326+
choose in which language to store the content.
327+
328+
Frontend Editing and multi-language
329+
-----------------------------------
330+
331+
When using the CreateBundle, you do not need to do anything at all to get
332+
multi-language support. PHPCR-ODM will deliver the document in the requested
333+
language, and the callback URL is generated in the request locale, leading to
334+
save the edited document in the same language as it was loaded.
335+
336+
.. note::
337+
338+
If a translation is missing, language fallback kicks in, both when viewing
339+
the page but also when saving the changes, so you always update the
340+
current locale.
341+
342+
It would make sense to offer the user the choice whether they want to
343+
create a new translation or update the existing one. There is this
344+
`issue`_ in the CreateBundle issue tracker.
345+
346+
.. _`LuneticsLocaleBundle`: https://github.com/lunetics/LocaleBundle/
347+
.. _`this discussion about ICU`: https://github.com/symfony/symfony/issues/5279#issuecomment-11710480
348+
.. _`cmf-sandbox config.yml file`: https://github.com/symfony-cmf/cmf-sandbox/blob/master/app/config/config.yml
349+
.. _`PHPCR-ODM documentation on multi-language`: http://docs.doctrine-project.org/projects/doctrine-phpcr-odm/en/latest/reference/multilang.html
350+
.. _`issue`: https://github.com/symfony-cmf/CreateBundle/issues/39

book/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ The Book
1010
simplecms
1111
static_content
1212
structuring_content
13+
handling_multilang
1314

1415
.. include:: map.rst.inc

book/installation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ Adding new pages
214214

215215
Symfony CMF SE does not provide any admin tools to create new pages. If you
216216
are interested in adding an admin UI one solution can be found in
217-
:doc:`../cookbook/creating_cms_using_cmf_and_sonata`. However if all you want
217+
:doc:`../cookbook/creating_a_cms/sonata-admin`. However, if all you want
218218
is a simple way to add new pages that you can then edit via the inline
219219
editing, then you can use the SimpleCmsBundle ``page`` migrator. The Symfony
220220
CMF SE ships with an example YAML file stored in

book/routing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ For more information on the Routing component of Symfony CMF, please refer to:
502502
* :doc:`../components/routing/introduction` for most of the actual functionality implementation
503503
* :doc:`../bundles/routing/introduction` for Symfony2 integration bundle for Routing Bundle
504504
* Symfony2's `Routing`_ component page
505-
* :doc:`../cookbook/handling_multilang_documents` for some notes on multilingual routing
505+
* :doc:`../book/handling_multilang` for some notes on multilingual routing
506506

507507
.. _`Doctrine ORM`: http://www.doctrine-project.org/projects/orm.html
508508
.. _`PHPCR-ODM`: http://www.doctrine-project.org/projects/phpcr-odm.html

book/static_content.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ SonataDoctrinePHPCRAdminBundle_, a back office generation tool.
6060
In ContentBundle, the required administration panels are already declared in
6161
the ``Admin`` folder and configured in ``Resources/config/admin.xml``, and
6262
will automatically be loaded if you install the SonataDoctrinePHPCRAdminBundle
63-
(refer to :doc:`../cookbook/creating_cms_using_cmf_and_sonata` for
63+
(refer to :doc:`../cookbook/creating_a_cms/sonata-admin` for
6464
instructions on that).
6565

6666
Configuration

book/structuring_content.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ SonataDoctrinePHPCRAdminBundle_.
164164

165165
The included administration panels are automatically available but need to
166166
be explicitly put on the dashboard if you want to use them. See
167-
:doc:`../cookbook/creating_cms_using_cmf_and_sonata` for instructions on how
167+
:doc:`../cookbook/creating_a_cms/sonata-admin` for instructions on how
168168
to install SonataDoctrinePHPCRAdminBundle.
169169

170170
Configuration

0 commit comments

Comments
 (0)