Ok, here we go:

Maybe I should explain the project a little: It is a system for managing online 
E-Learning courses. It has several customers that can be assigned to courses. 
The customers can manage their courses in coursecategories. Courses and 
coursecategories have titles and descriptions which are translated. Originally, 
we had German and English, so I did it the "Jobeet way". Then a customer said, 
they wanted to also store the data also in Simplified Chinese. Thats when I 
started to rebuild the translation system. Now, I have a system, where i can 
simply add a language in my configuration file and be done with it. Here is how 
it works:


1. in the apps settings.yml, I add the available cultures, like
...
application_cultures: 
      code:                 [en, de, zh]
      name:                 [English, Deutsch, Simp.Chinese]

2. In the schema.yml I have to set the "required" option for the translated 
values to false, so that I won't get an error on saving the form.

3. Then, I had to write an custom validator, that validates that a title (or 
any other mandatory field) is set in at least one language.

In configure() of the main Form (not the I18n):
...
$cultures=sfConfig::get('sf_application_cultures');

$this->embedI18n($cultures['code']);
for($i=0;$i<count($cultures['code']);$i++)
{
  $this->widgetSchema->setLabel($cultures['code'][$i], $cultures['name'][$i]);
}
$this->validatorSchema->setPostValidator(
  new sfValidatorCallback(array('callback' => array($this, 'validateOneTitle')))
);
...

And then write a method validateOneTitle()
public function validateOneTitle($validator, $values)
{
  $cultures=sfConfig::get('sf_application_cultures');
  for($i=0;$i<count($cultures['code']);$i++)
  {
    if(!empty($values[$cultures['code'][$i]]['title']))
    {
      return $values;
    }
  }
  
  throw new sfValidatorError($validator, 'You need to enter the title in the 
language, you are editing.');
}

That is all in the form class.

Now we have to add a small filter form that can be used to trigger the ajax 
call. It looks like this:

class LanguageFilterForm extends BaseForm
{
  public function configure()
  {
    $cultures=sfConfig::get('sf_application_cultures');

    for($i=0;$i<count($cultures['code']);$i++){
      $mycultures[$cultures['code'][$i]]=$cultures['name'][$i];
    }

    $this->widgetSchema['languages']=new 
sfWidgetFormSelect(array('choices'=>$mycultures));
  }
}

The more tricky parts are the template and the action class. Lets start with 
the template:
In the editSuccess.php the div for the form that is reloaded by the ajax-call 
is located. I do all my ajax-stuff with jquery. In my example, I use the form 
for a above explained "Coursecategory".

<div id="canvas">
  <img id="loader" src="/images/loader.gif" style="vertical-align: middle; 
display: none" />
  <?php include_partial('form', 
array('form'=>$form,'editingLanguageCode'=>$editingLanguageCode,'editingLanguageName'=>$editingLanguageName,'coursecategory'=>$coursecategory))?>
</div>

<?php slot('col2_content') ?>   
  <?php include_partial('global/ajaxLanguageForm', 
array('module'=>$sf_context->getModuleName(),'action'=>'edit','object'=>$coursecategory,'languageForm'=>$languageForm))?>
<?php end_slot() ?>

We pass all the required data to the form partial (the form object, the 
coursecategory object (which of course isn't nessessary, but "grown" code), and 
the languagecode and name).

The partial in the slot is a dropdownbox, that I include in all forms for 
translated Objects, therefor it resides in the  layout folder and gets the 
module and action passed, as well as the LanguageFilterForm. The code of the 
partial is like:

<?php echo javascript_tag() ?>
$(document).ready(function()
{
  $("#languages").change(function()
  {
        $('#loader').show();

    var myLang=$(this).children('option:selected').val();
    $("#canvas").load(
      $(this).parents('form').attr('action'),
      { lang: myLang },
      function() { $('#loader').hide(); }
    );
  });
});
<?php end_javascript_tag() ?>

<form action="<?php echo url_for($module.'/'.$action.(!$object->isNew() ? 
'?id='.$object->getId() : '')) ?>" method="post">
        <?php echo $languageForm['languages']->renderError() ?>
        <?php echo $languageForm['languages'] ?>
        <?php echo $languageForm['_csrf_token']?>
</form>

Now the form partial is still missing. I leave this code out, since it is a 
little long. The important thing is that the form is not rendered by <?php echo 
$form?> but as single form fields. I split the form into two partials, one is 
the form with non translated values and the other is the form with 
translations. So in the main form, I include:

<?php include_partial('i18nForm', 
array('form'=>$form,'editingLanguageCode'=>$editingLanguageCode));?>

The I18nForm partial now needs to change the language code in the form fields 
to store the translated values like:

<tr>
      <th>
        <label for="coursecategory_<?php echo 
$editingLanguageCode?>_title"><?php echo __('Title') ?> *</label>
      </th>
      <td>
        <div class="error_message"><?php echo 
$form[$editingLanguageCode]['title']->renderError() ?></div>
        <?php echo $form[$editingLanguageCode]['title']->render(array('class' 
=> 'input')) ?>
      </td>
    </tr>

Thats all in the templates, only the action is missing. Since i have to process 
two forms (languageform and Objectform), I placed  the form creation code in a 
separate method. Most of the code is pretty standard.

  public function executeNew(sfWebRequest $request)
  {
    $this->getForms($request->getParameter('lang'));
  }

  public function executeCreate(sfWebRequest $request)
  {
    $this->forward404Unless($request->isMethod(sfRequest::POST));

    $this->getForms($request->getParameter('lang'));

    $this->processForm($request, $this->form);

    $this->setTemplate('new');
  }

  /**
   * Edit a Coursecategory
   *
   * @param sfWebRequest $request A request object
   */
  public function executeEdit(sfWebRequest $request)
  {
    
$this->coursecategory=CoursecategoryPeer::retrieveByPk($request->getParameter('id'));

    $this->getForms($request->getParameter('lang'),$this->coursecategory);

    if($request->isXmlHttpRequest())
    {
      return $this->renderPartial('coursecategory/form', array('form' => 
$this->form,'coursecategory'=>$this->coursecategory,'editingLanguageCode'=>$this->editingLanguageCode,'editingLanguageName'=>$this->editingLanguageName));
    }    
  }

  public function executeUpdate(sfWebRequest $request)
  {
    $this->forward404Unless($request->isMethod(sfRequest::POST) || 
$request->isMethod(sfRequest::PUT));
    $this->forward404Unless($Coursecategory = 
CoursecategoryPeer::retrieveByPk($request->getParameter('id')), sprintf('Object 
Coursecategory does not exist (%s).', $request->getParameter('id')));

    $this->getForms($request->getParameter('lang'),$Coursecategory);

    $this->processForm($request, $this->form);

    $this->setTemplate('edit');
  }

  protected function processForm(sfWebRequest $request, sfForm $form)
  {
    $form->bind($request->getParameter($form->getName()), 
$request->getFiles($form->getName()));
    if ($form->isValid())
    {
      $this->getUser()->setFlash('notice', 'Coursecategory saved 
successfully.');
      $Coursecategory = $form->save();

      $this->redirect('@coursecategory');
    }
  }

  protected function getForms($lang=null,$coursecategory=null)
  {
    $editingLanguage=$this->getUser()->getEditingLanguage($lang);
    $this->editingLanguageCode=$editingLanguage['code'];
    $this->editingLanguageName=$editingLanguage['name'];

    $this->form = new CoursecategoryForm($coursecategory);

    $this->languageForm=new LanguageFilterForm();
    $this->languageForm->setDefault('languages',$this->editingLanguageCode);
  }

Last thing, I forgot was that the default language should be the one, the user 
selected for himself, so that is the call to 
this->getUser()->getEditingLanguage($lang);

In myUser.class.php

  public function getEditingLanguage($lang=null)
  {
    $cultures=sfConfig::get('sf_application_cultures');

    $editingLanguage['code']=$this->getCulture();

    if(!is_null($lang))
    {
      $editingLanguage['code']=$lang;
    }

    for($i=0;$i<count($cultures['code']);$i++){
      if($cultures['code'][$i]==$editingLanguage['code'])
      {
        $editingLanguage['name']=$cultures['name'][$i];
      }
    }
    
    return $editingLanguage;
  }

That's all, I hope I didn't forget anything. If anything is unclear, feel free 
to ask. Here is also some screenshot of the form:
http://www.mda.ch/lms/Bildschirmfoto.png
http://www.mda.ch/lms/Bildschirmfoto-1.png

Regards,
Christopher.


-----Ursprüngliche Nachricht-----
Von: symfony-users@googlegroups.com [mailto:symfony-us...@googlegroups.com] Im 
Auftrag von Tom Ptacnik
Gesendet: Donnerstag, 10. Juni 2010 10:10
An: symfony users
Betreff: [symfony-users] Re: Managing i18n content for frontend in the backend

I prefer codewise, but if you can show me also how it look in action,
I don't mind :)



On 9 čvn, 08:08, "Christopher Schnell" <ty...@mda.ch> wrote:
> Hi,
>
> you like to see it codewise or in action?
>
> Regards,
> Christopher.
>
> -----Ursprüngliche Nachricht-----
> Von: symfony-users@googlegroups.com [mailto:symfony-us...@googlegroups.com] 
> Im Auftrag von Tom Ptacnik
> Gesendet: Dienstag, 8. Juni 2010 16:03
> An: symfony users
> Betreff: [symfony-users] Re: Managing i18n content for frontend in the backend
>
> Hi,
>
> thank you for your response.
>
> I've managed this today as I sad in my first report (one translation
> on the form via mergeForm, selected language is set in the session),
> but I would realy like to see your solution.
>
> Regards, Tom
>
> On 8 čvn, 09:17, "Christopher Schnell" <ty...@mda.ch> wrote:
>
>
>
>
>
> > Hi,
>
> > I've done something like this, but with an AJAX approach. The user can
> > select the language he wants to edit and the form adjusts to the selected
> > language. Then I only have to validate that at least one language does exist
> > and adjust the __toString() methods of translated objects to provide
> > language fallback.
>
> > It is a little tricky, but it works. If you need more information, I will
> > try to provide some detailed examples. The advantage is, that you don't have
> > to get the language in the form, but rather render only the form, required.
>
> > Hope, this helps a little.
>
> > Regards,
> > Christopher.
>
> > -----Ursprüngliche Nachricht-----
> > Von: symfony-users@googlegroups.com [mailto:symfony-us...@googlegroups.com]
> > Im Auftrag von Tom Ptacnik
> > Gesendet: Dienstag, 8. Juni 2010 08:54
> > An: symfony users
> > Betreff: [symfony-users] Managing i18n content for frontend in the backend
>
> > Hi,
>
> > I want to tell you my thoughts about managing I18N content in the
> > backend and want to know your opinions.
>
> > I want to internationalize my frontend app - classic (i18n/
> > messages.xx.xml + object with I18n behaviour) .. no problem
>
> > Then I need to manage internationalized objects (News) in the backend.
> > I have attributes: title and content  (doctrine - i18n behaviour).
> > I want English, Deutch and Czech language.
>
> > I need to somehow to display this languages on the form.
>
> > possibility 1)
> > In the master form use $this->embedI18n(array('en', 'de', 'cs')); ..
> > like in Jobeet example.
> > I don't like this solution because the form is too long. 2 fields for
> > every language. (imagine that I want to add some mor languages, or
> > more internationalized fields ... I thing this is not suitable for me)
>
> > possibility 2)
> > Show only fields for selected translation. There is some solution here
> > [url1]http://forum.symfony-project.org/index.php/t/16823/... I
> > almost like it.
>
> > I want to do something like this [url1]. For this I can use
> > embedI18n(array('onlyOneSelectedLang')) or
> > mergeForm(xxFormTranslation())
> > I think I'll go with mergeForm() if I don't hit a snag with something.
>
> > One thing I dont like on the solution in [url1]  is that he use use
> > sf_culture in the sfUser object for selecting languege and
> > sf_default_culture setting. I don't think that is correct, because
> > this stuff is for internationalizing the backend, not for the managing
> > the internationalized content. Am I right?
>
> > So if I don't want to use sf_culture -  I have to figer out how to
> > KNOW which language is selected .. which translation show on the form.
>
> > I've thought about two solutions
> > 1) store it into the session -> something like sf_culture in sf_user
> > object
> > 2) store in only in the URL ( backend_dev.php/news/1/edit/
> > edited_lang )
>
> > If you have read this till this end and you've some opinion or you
> > have deal with something similar .. tell me how you did it.
>
> > I want to do this:
> > - Show some tabs-like links for changing the language of the form
> > content (EN, DE, CZ)
> > - Store this selected lang into the session (contentCulture)
> > - On the form show only the fields for the selected lang by
> > mergeForm() function
>
> > What do you thing abou it?
>
> > --
> > If you want to report a vulnerability issue on symfony, please send it to
> > security at symfony-project.com
>
> > You received this message because you are subscribed to the Google
> > Groups "symfony users" group.
> > To post to this group, send email to symfony-users@googlegroups.com
> > To unsubscribe from this group, send email to
> > symfony-users+unsubscr...@googlegroups.com
> > For more options, visit this group 
> > athttp://groups.google.com/group/symfony-users?hl=en
>
> --
> If you want to report a vulnerability issue on symfony, please send it to 
> security at symfony-project.com
>
> You received this message because you are subscribed to the Google
> Groups "symfony users" group.
> To post to this group, send email to symfony-users@googlegroups.com
> To unsubscribe from this group, send email to
> symfony-users+unsubscr...@googlegroups.com
> For more options, visit this group 
> athttp://groups.google.com/group/symfony-users?hl=en

-- 
If you want to report a vulnerability issue on symfony, please send it to 
security at symfony-project.com

You received this message because you are subscribed to the Google
Groups "symfony users" group.
To post to this group, send email to symfony-users@googlegroups.com
To unsubscribe from this group, send email to
symfony-users+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/symfony-users?hl=en

-- 
If you want to report a vulnerability issue on symfony, please send it to 
security at symfony-project.com

You received this message because you are subscribed to the Google
Groups "symfony users" group.
To post to this group, send email to symfony-users@googlegroups.com
To unsubscribe from this group, send email to
symfony-users+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/symfony-users?hl=en

Reply via email to