can you get more random?

10 000 sentences, the easy way

June 7th, 2013 Posted in languages, tech | No Comments »

Apart from spending copious amounts of time learning Spanish German, I’ve also been researching the most effective methods for language learning. Meta-language learning, I guess.

My first discovery was Read the rest of this entry »

Share

Symfony i18n : dumping and loading translation strings

September 13th, 2012 Posted in tech | No Comments »

I’ve been meaning to write about this for literally years, but never got round to it. Hopefully this will be useful to someone. After an only partially sucessful attempt to manage symfony i18n strings in a MySQL DB, I finally gave up and reverted back to the default XLIFF file method. One of the reasons for initially wanting to store the translations in a DB was to build an easy to use interface to allow translators to easily find untranslated strings. Unfortunatley this didn’t work as well as I was hoping, and the alternatives (installing pootle, or getting translators to install and learn how to use an xliff tool) werent’ much better.

I finally decided that the easiest way to update translations would be by dumping untranslated strings to a file before sending them to a translator. Unfortunately I couldn’t find a way to easily dump strings to a file and reload translated strings, so I wrote a couple of new tasks to do this for me.

You need to create the following two files: lib/tasks/sfI18nDumpTask.class.php


<?php
/*
* Current known limitations:
* - Can only works with the default "messages" catalogue
* - For file backends (XLIFF and gettext), it only saves/deletes strings in the "most global" file
*/

/**
* Dumps i18n strings from messages.xml files.
*
*/
class sfI18nDumpTask extends sfBaseTask
{
/**
* @see sfTask
*/
protected function configure()
{
$this->addArguments(array(
new sfCommandArgument('application', sfCommandArgument::REQUIRED, 'The application name'),
new sfCommandArgument('culture', sfCommandArgument::REQUIRED, 'The target culture'),
));

$this->addOptions(array(
new sfCommandOption('untranslated', null, sfCommandOption::PARAMETER_NONE, 'Output all new found strings'),
));

$this->namespace = 'i18n';
$this->name = 'dump';
$this->briefDescription = 'dumps i18n strings from messages.xml files';

$this->detailedDescription = <<<EOF
The [i18n:dump|INFO] task extracts i18n strings from your project files
for the given application and target culture:

[./symfony i18n:dump frontend fr|INFO]

By default, the task dumps all translations it found in the current project.

[./symfony i18n:dump frontend fr|INFO]

If you want to dump the untranslated strings, use the [--untranslated|COMMENT] option:

[./symfony i18n:dump --untranslated frontend fr|INFO]

EOF;
}

public function execute($arguments = array(), $options = array())
{
$this->logSection('i18n', sprintf('dumping i18n strings for the "%s" application', $arguments['application']));

// get i18n configuration from factories.yml
$config = sfFactoryConfigHandler::getConfiguration($this->configuration->getConfigPaths('config/factories.yml'));

$class = $config['i18n']['class'];
$params = $config['i18n']['param'];
unset($params['cache']);

$i18n = new $class($this->configuration, new sfNoCache(), $params);
$i18n->getMessageSource()->setCulture($arguments['culture']);
$i18n->getMessageSource()->load();

$messages = $i18n->getMessageSource()->read();

$count=0;
foreach ($messages as $catalogue => $translations) {
$this->logSection('i18n', sprintf('found "%d" i18n strings', count($translations)));
foreach ($translations as $key => $values) {
if ($options['untranslated'] && ('' != $values[0])) {
continue;
}
$count++;
$source = str_replace('"', '""', $key);
$translation = str_replace('"', '""', $values[0]);
print "$values[1]|\"$arguments[culture]\"|\"$source\"|\"$translation\"\n";
}
}

$this->logSection('i18n', sprintf('dumped "%d" i18n strings', $count));

}
}

and lib/task/sfI18nLoadTask.class.php


<?php

/*
* Current known limitations:
* - Can only works with the default "messages" catalogue
* - For file backends (XLIFF and gettext), it only saves/deletes strings in the "most global" file
*/

/**
* loads i18n strings from php files.
*
*/
class sfI18nLoadTask extends sfBaseTask
{
/**
* @see sfTask
*/
protected function configure()
{
$this->addArguments(array(
new sfCommandArgument('application', sfCommandArgument::REQUIRED, 'The application name'),
new sfCommandArgument('culture', sfCommandArgument::REQUIRED, 'The target culture'),
new sfCommandArgument('filename', sfCommandArgument::REQUIRED, 'The translation csv file'),
));

$this->addOptions(array(
new sfCommandOption('no-overwrite', null, sfCommandOption::PARAMETER_NONE, 'Dont overwrite existing translations'),
));

$this->namespace = 'i18n';
$this->name = 'load';
$this->briefDescription = 'loads i18n strings from php files';

$this->detailedDescription = <<<EOF
The [i18n:load|INFO] task loads i18n strings from your project files
for the given application and target culture:

[./symfony i18n:load frontend fr filename|INFO]

By default, the task will load all strings from the file
into the current project.

If you only want to load previously untranslated strings, use the [--no-overwrite|COMMENT] option:

[./symfony i18n:load --no-overwrite frontend fr filename|INFO]

EOF;
}

/**
* @see sfTask
*/
public function execute($arguments = array(), $options = array())
{
$this->logSection('i18n', sprintf('loading i18n strings for the "%s" application', $arguments['application']));

// get i18n configuration from factories.yml
$config = sfFactoryConfigHandler::getConfiguration($this->configuration->getConfigPaths('config/factories.yml'));

$class = $config['i18n']['class'];
$params = $config['i18n']['param'];
unset($params['cache']);

$i18n = new $class($this->configuration, new sfNoCache(), $params);
$i18n->getMessageSource()->setCulture($arguments['culture']);
$i18n->getMessageSource()->load();

$messageSource = $i18n->getMessageSource();
$messages = $messageSource->read();

if ($loaded_list = file($arguments['filename'])) {
foreach ($loaded_list as $line) {
$line = chop($line);
if (preg_match('/(\d+)\|"(.*)"\|"(.*)"\|"(.*)"/', $line, $matches)) {
list($id, $culture, $source, $translation) = array($matches[1], $matches[2], $matches[3], $matches[4]);
if ($culture == $arguments['culture']) {
$messageSource->update($source, $translation, '');
}
}
}
} else {
$this->logSection('i18n', sprintf('Cant open file "%s"', $arguments['filename']));
}

if ($options['no-overwrite'])
{
$this->logSection('i18n', 'saving only new i18n strings');
} else {
}
$messageSource->save();

}
}

Before dumping the new strings to a file, you should first add any new untranslated strings using the builtin symfony i18n:extract command:

 ./symfony i18n:extract --auto-save --display-new frontend <culture> 

Then you’re ready to dump the new untranslated strings to a file :

 ./symfony i18n:dump frontend <culture> --untranslated > untranslated.csv 

You can then import this file into your favourite spreadsheet program. In NeoOffice this is done by opening a fresh spreadsheet and importing the data via “Insert” > “Sheet from external file” and making sure to add pipe ‘|’ as field seperator. YMMV in your spreadsheet tool of choice. Once the translations have been added in the 4th column of the spreadsheet, save the document as a pipe separated csv file before using the following command to reload the translated strings:

 ./symfony i18n:dump frontend <culture> translated.csv 
Share

Word of the day : Hyperpolyglotist

April 12th, 2012 Posted in german, languages | No Comments »

Talking about language learning, as I was, I found an article on the BBC website about hyperpolyglotism. I was pretty happy with my acceptable french, moderate spanish and fairly miserable german until I saw that video.

Share

Learning German with Anki

April 12th, 2012 Posted in german, languages | 6 Comments »

I’ve been told by several people that German is an easy foreign language for English speakers to learn. My experience in the past few months leads me to believe otherwise. I always thought that German was a logical language where it was enough to follow the rules. Well that may be true, but there are hundreds of rules and as many exceptions.

Despite this, I haven’t been put off, and fairly early on I discovered Anki which is an incredible tool to help with learning vocabulary (Apparently this kind or tool is called an SRS or Spaced repetition system. Flashcards to you and I). Vocab learning was never my strong point, but Anki really helps. Whether I have 10 minutes on the U-Bahn or an hour in the evening, I can quickly assimilate a few dozen more words.

However the dearth of dictionary files was an initial problem, so I have created a beginners file from various sources, which may be of use to any other beginning German learners. Check out my English-German Anki deck. I’ve been adding to this file for the past few months, and I hope it is as error-free as possible, but if you do find any errors, please let me know and I’ll correct them.

I’ve also uploaded the file to Google docs, if anyone wants write access to modify/improve the data, let me know. I’ll update the anki file with the latest greatest version.

Share

Bordeaux restaurants

September 27th, 2010 Posted in food and drink | No Comments »

Sitting at home in Paris on a cold wet september night, those three weeks of holiday in August seem like a distant memory. We travelled from Paris to Bordeaux, visiting the surrounding area, then up into the Pyrennées for a few days in the mountains, before continuing down the catalan coast via Cadaques to Barcelona.

In that short time we discovered some incredible restaurants. If you’re in the area, here’s a summary of the best discoveries.

Bordeaux

Bordeaux is a city that has undergone a revolution in the past 10 years. Until the end of the ’90s the centre of the city had a dirty, dusty, polluted feel to it. Since the beginning of the new millenium however, it has been reborn, after the car was banished from the narrow streets of the centre and the exteriors of most of the buildings were given a good clean. Today the town is much more welcoming, if somewhat touristic.

If you’re in Bordeaux for a few days, try to visit some of the following restaurants:

  • Le petit commerce – situated in a tiny pedestrian steet between the Place st. Pierre and Place du Parlement, this bistrot-style restaurant is open for lunch and dinner throughout the week. The lunchtime menu is a bargain at 12 €. If the weather is good, try to arrive early to get a seat on the terrace.
  • For something a little more traditional, try Le plat a oreilles. This small family-run restaurant is somewhat out of place on Rue des Faussets amongst a bunch of mediocre ‘attrape-touriste’ restaurants. Open every evening, the menu at 25€ is great value. Everything (from the starter to the desert) is home-made. the house wine was respectable too and reasonably priced
  • I was a little dissapointed by le Bô Bar. After having read the review in Le guide du Fooding I was curious to discover their concept – the wine is served anonymously by the glass, with the only choice being between a light, medium, full-bodied red, or a fruity or dry white. No other clues are given as to the provenance of the wine. While all of the wines we tasted were wonderful, the food was a disapointment, I had the impression our meal was reheated in a microwave, the dry congealed rice was simply unappetizing.
  • On sunday morning, you can’t do better than Karl in Place du parlement. We arrived early and were lucky to get a seat in the sun on the terrace, but 1/2 hour later and people were queueing up to get a seat inside. An enourmous selection and huge portions, highly recommended

Its late so I’ll stop there. Tomorrow I’ll try and jot down some of the best restaurants in Arcachon, Saillagouse, Cadaques and Barcelona

Share

Symfony: extracting strings from 1.2 forms using i18n:extract

June 9th, 2010 Posted in tech | No Comments »

Anyone using Sympfony 1.2 will probably have realised by now that the i18n:extract task doesn’t extract strings from any files in lib/form/*

This useful thread shows how to dump all translated strings into a file which can later be parsed by the i18n:extract task, unfortunately it creates huge files, and needs to be disabled in production environments for performance reasons.

After a quick search through the code, I  have patched my code to only dump untranslated strings to the i18n.temp file.

Here’s the patch for lib/vendor/symfony/lib/i18n/sfMessageFormat.class.php

// found, but untranslated
 if (empty($target))
 {
+          file_put_contents(sfConfig::get('sf_app_template_dir').'/i18n.temp.php',"<?php __('$string'); ?>\n",FILE_APPEND);
 return $this->postscript[0].$this->replaceArgs($string, $args).$this->postscript[1];
 }
 return $this->replaceArgs($target, $args);

(I would have posted this in reply to the original comment, but I couldn’t find the original post for some reason)

Share

I18n of sfSimpleForumPlugin

January 19th, 2009 Posted in tech | No Comments »

The sfSimpleForum plugin is a great way to quickly add a forum to a site, but it doesn’t handle multi-lingual sites, so here are my notes on how to add this feature.

1. Edit plugins/sfSimpleForumPlugin/config/schema.yml, add the culture field to the sf_simple_forum_category table :

lang:     { type: varchar, size: 7 }

My thinking is that only the categories need an added lang field, as all forums belong to a category, and all posts belong to a forum, so the language of a post is implicit in its forum and category. Note I’m using ‘lang’ and not ‘culture’ here, as culture seems to have a specific meaning in symfony, and is used when an object can exist with several cultures, so a category would exist in all languages. However in this case, the french site could have a diferent set of categories to the english site, so it’s not appropriate to use the field name ‘culture’.

2. Add culture to the test data :

sfSimpleForumCategory:
  c1:
    name:        Public Boards
    description: We talk about stuff here.
    rank:        1
    culture:     en_GB
  c2:
    name:        Miscellaneous
    description: Secret matters
    rank:        2
    culture:     en_GB
  c3:
    name:        Alleatoire
    description: Les choses alleatoires
    rank:        1
    culture:     fr_FR

3. Select by culture when getting list of all categories in plugins/sfSimpleForumPlugin/lib/model/plugin/PluginsfSimpleForumForumPeer.php:

public static function getAllOrderedByCategory()
{
  $c = new Criteria();
  $c->addJoin(self::CATEGORY_ID, sfSimpleForumCategoryPeer::ID);
  $c->add(sfSimpleForumCategoryPeer::LANG, sfContext::getInstance()->getUser()->getCulture());
  $c->addAscendingOrderByColumn(sfSimpleForumCategoryPeer::RANK);
  $c->addAscendingOrderByColumn(self::RANK);return self::doSelectJoinCategoryLeftJoinPost($c);
}

4. Add culture to admin interface in plugins/sfSimpleForumPlugin/modules/sfSimpleForumCategoryAdmin/config/generator.yml :

list:
  title: Category Administration
  display: [=name, description, rank, lang]
edit:
  title: Edit category "%%name%%"
  display: [name, description, rank, _lang, _forums]

5. Add set_culture partial in plugins/sfSimpleForumPlugin/modules/sfSimpleForumCategoryAdmin/templates/_lang.php :

< ?php
use_helper('Utils');
$cultures = array('en_GB' => 'English', 'fr_FR' => 'French', 'de_DE' => 'German');
echo select_culture_tag('sf_simple_forum_category[lang]', $sf_simple_forum_category->getLang(), $cultures, array('id' => 'lang'));

6. add select_culture_tag function in apps/backend/lib/helper/UtilsHelper.php :

function select_culture_tag($name, $selected = null, $cultures = array(), $options = array())
{
$c = new sfCultureInfo(sfContext::getInstance()->getUser()->getCulture());
if (! is_array($cultures)) {
$cultures = $c->getCultures();
}
if ($culture_option = _get_option($options, 'cultures'))
{
foreach ($cultures as $key => $value)
{
if (!in_array($key, $culture_option))
{
unset($cultures[$key]);
}
}
}
asort($cultures);
$option_tags = options_for_select($cultures, $selected);
return select_tag($name, $option_tags, $options);
}

7. Rebuild model and you’re good to go:

symfony propel:build-model
Share

Test from phone

January 19th, 2009 Posted in tech | No Comments »

Not much to say, the clue is in the title ;)

Share

Annapurna Circuit

December 2nd, 2008 Posted in travel | No Comments »

Britta suggested doing the Annapurna trail sometime early next year. As part of my initial research, here’s some links with more information :

If you know of any more good sources of information, let me know

Share

The world’s ugliest buildings

November 15th, 2008 Posted in General | No Comments »

Virtual Tourist have just published a press release listing (in their opinion) the worlds ugliest buildings. While you can argue with their choice (I quite like Liverpool Cathedral and I don’t find Tour Montparnasse in Paris that much of an eyesore), it’s a shame they didn’t spend ore time promoting the article on their site. the link from the article at Yahoo ! went straight to the reuters site, and I had to spend a few minutes on VirtualTourist before I found the press release. It would have been nice to have the article promoted in the virtual tourist home page, with links to photos on their site. Yet another missed opportunity I guess.

Hmm what to do today. As I’m in Barcelona, I think I’ll have lunch at Quimet & Quimet, a wonderful little tapas bar in Poble Sec. Or maybe I’ll just spend the rest of the morning looking out over Plaça Vicenç Martorell here at Chelo, with another one of their amazing fruit juices.

Back to work on monday. What joy!

Share