Showing posts with label PHP. Show all posts
Showing posts with label PHP. Show all posts

Tuesday, January 15, 2008

Gettext for PHP and Javascript

I feel like a spy returning from the cold. I haven't visited this blog in quite a few months, mostly because writing blogs is difficult. I must say after writing my first 2 posts I have a whole new appreciation for bloggers our there, who I mocked and dissed over years. But as a web programmer I bump into weird problems almost every week, and after I solve them I always tell myself "someone should write about this." So I finally picked up the courage to return here again. But enough of that lets get to business.

Recently we have been internationalizing (aka i18n, l10n) our website Traxtuff. Like most programmers we began by producing a detailed design of our dream translation framework for PHP, but a quick search in google made us choose gettext. There are many good articles about using gettext with PHP(Here's one and here's another, Pablo Hoch also posted a benchmark of gettext). Even though it's far from perfect, it's widely used in many projects, so we decided to use it. We added some nice helper functions to wrap _() (aka gettext()):


function _L($str)
{
echo _($str);
}

function _S()
{
if (func_num_args() == 0) {return;}
if (func_num_args() == 1) {_L(func_get_arg(0));return;}
$args = func_get_args();
$args[0] = _(func_get_arg(0));
echo call_user_func_array('sprintf', $args);
}

function _T()
{
if (func_num_args() == 0) {return false;}
if (func_num_args() == 1) {return _(func_get_arg(0));}
$args = func_get_args();
$args[0] = _(func_get_arg(0));
return call_user_func_array('sprintf', $args);
}


Don't ask what L, S & T stand for, I just chose letters randomly :) .

We then had to decide what to do with internationalized strings inside our javascript files. I bumped (completely by accident while researching something else) upon a cool implementation of gettext for Javascript. Here is a link to it. I admit it's not very well documented (major understatement), but it's pretty simple to use. To get the code you need to go into the code repository here. This code depends upon the ubiquitous prototype javascript framework. Basically what you need to do is add in your HTML a link to your po (POT) file:

<link rel="gettext" lang="de" href="[path to your po file]"></link>

And after it in your scripts:
Gettext.lang = 'de';
Gettext.gettext('hello %s','world');
_('hello %s','world');


Yonatan couldn't make this work, so he added before this the code:

Gettext.include.apply(Gettext, Gettext.links[Gettext.linksPointer]);
Gettext.linksPointer++;

Hope this works for you. Let us know if you find something more elegant.

Everything was working peachy, until we upgraded to prototype 1.6. I spent a few hours today trying to figure out why gettext.js stopped working. Finally it turned out that the prototype function Array.indexOf doesn't work in the same way (hope I'm not saying something stupid). Previously it supported finding elements in an array like this:

var arr = [["a"], ["b"]];
arr.indexOf("a");


But now it only supported flat arrays (I think this is because prototype.js first checks if the browser already has an implementation of Array.indexOf). So I fixed gettext.js by adding flatten() call before calling indexOf for the message arrays. It's just a small change in 2 places, so it's not too scary :).

That's all, happy hacking for all of you.

p.s. We recommend creating a separate po files for your javascripts, so it will be as small as possible. After all this gettext implementation isn't very efficient.

Thursday, March 8, 2007

Selenium IDE - format for CakePHP Selenium helper

This is a companion post to Daniel Hofstetter's (author of cakebaker blog), about cakephp selenium helper.

OpenQA Selenium Core is a testing framework for web applications.
Cakephp is a MVC framework for php.
SeleniumIDE is a firefox extension that allows recording and running of selenium test cases within the browser.

The Cakephp Selenium helper allows writing test cases for selenium in php code, instead of the annoying html table style "selenese". I was using it for a while before I tried out the SeleniumIDE firefox extension. I found out that it had an export capability that created code for different languages like ruby and perl, but no cakephp :(. Luckily writing a new formatter is fairly easy.

Here is the code for an implementation of a cakephp formatter. It's not perfect, and doesn't add a line for creating the testCase name, but that should be pretty easy to add:


/**
* Parse source and update TestCase. Throw an exception if any error occurs.
*
* @param testCase TestCase to update
* @param source The source to parse
*/

function parse(testCase, source) {
}

/**
* Format TestCase and return the source.
*
* @param testCase TestCase to format
* @param name The name of the test case, if any. It may be used to
embed title into the source.
*/

function format(testCase, name) {
var result = '<?php\n';

var commands = testCase.commands;
result += formatCommands(commands);
result += '?>';

return result;
}

/**
* Format an array of commands to the snippet of source.
* Used to copy the source into the clipboard.
*
* @param The array of commands to sort.
*/

function formatCommands(commands) {
var result = '';
for (var i = 0; i < commands.length; i++) {
var command = commands[i];
if (command.type == 'command') {
if (command.value !== "") {
result += '$selenium->' + command.command
+ '(\'' + command.target + '\',\'' + command.value + '\');\n';
}
else {
result += '$selenium->' + command.command
+
'(\'' + command.target + '\');\n';
}
}
}

return result;
}

/*
* Optional: The customizable option that can be used in
format/parse functions.
*/

//options = {nameOfTheOption: 'The Default Value'}

/*
* Optional: XUL XML String for the UI of the options dialog
*/

//configForm = '<textbox id="options_nameOfTheOption"/>'


To add a formatter to SeleniumIDE follow the following tutorial:

1. Open the SeleniumIDE extension, and choose options in the menu


2. Click the "add" button


3. Paste the code presented above in the textarea


4. Record your test, and then you can choose "File/Export Test As/cakephp"


And something like


$selenium->open('/');
$selenium->clickAndWait('link=test problem');
$selenium->clickAndWait('img_avatar');
$selenium->clickAndWait('sign_in');
$selenium->type('UserUsername','bla');
$selenium->type('PasswordPassword','bla');
$selenium->click('rememberme');
$selenium->clickAndWait('submit');
?>


is generated.

If you would like to open an existing cakephp test in SeleniumIDE, you don't need to write a parser, just "open" the url for your test, for example "http://localhost/pages/tests/test1" and your testcase will generate the table that is parsed by SeleniumIDE, you can make your changes, and export it back to cakephp.



Tags: ,

Selenium Core 0.8.2 testing framework - fixing bug of pause/continue button not working properly

I was using this nifty web application testing framework (opensource) for a while before the following bug became intolerable, and I searched for a solution on google. Hopefully this blog post will save you some trouble:

After pressing the pause button, pressing continue doesn't actually resume the run, but only does a single "step" and stops again!

How to solve this bug? Edit the file /scripts/selenium-testrunner.js and find the function reset extending the object HtmlTestRunnerControlPanel, around line 345.

Remove the comment on the line:

// this.runInterval = this.speedController.value;

=>

this.runInterval = this.speedController.value;



Voila! Pressing continue should now run smoothly.

p.s.

1. Apparently they already fixed this in the trunk:
http://svn.openqa.org/fisheye/browse/selenium/trunk/code/javascript/core/scripts/selenium-testrunner.js?r1=1690&r2=1742

2. This is the entry in the openqa forum where I found the solution: http://forums.openqa.org/thread.jspa?threadID=6163

Tags: