7

I am trying to set up a way to allow members to translate strings into other languages. You can see an example here: TRANSLATIONS TEST

Someone recommended that I use php's native gettext() function for this, instead of what I am already using to load the language files, which is this:

function loadLanguageFile($language, $file) {

    $temp = array();

    $data = file_get_contents('./'.$language.'/'.$file.'.'.$language.'.php');

    $codes = array (
        '/(\'\s*\.\s*\$)(.+?)(\s*\.\s*\')/',
        '/(=\s*\$)(.+?)(\s*\.\s*\')/',
        '/(\'\s*\.\s*\$)(.+?)(;)/',
        '/(\[\')(.+?)(\'\])/',
        '/<\?php/s', '/\?>/s', '/<\?/s'
    );
    $html = array (
        '{$2}',
        '= \'{$2}',
        '{$2}\';',
        '[$2]',
        '',
    );
    // Since we don't have the values for the vars.
    $data = preg_replace($codes, $html, $data);

    // We must change this because they are global.
    $data = str_replace('$txt', '$langEditor_txt', $data);
    $data = str_replace('$helptxt', '$langEditor_helptxt', $data);

    eval($data);

    if (isset($langEditor_txt)) {
        $temp['txt'] = $langEditor_txt;
        unset($GLOBALS['langEditor_txt']);
    }
    if (isset($langEditor_helptxt)) {
        $temp['helptxt'] = $langEditor_helptxt;
        unset($GLOBALS['langEditor_helptxt']);
    }

    return $temp;
}

The strings are contained within a file that is named like so: ManageDPModules.english.php DreamPortal.english.php etc.

These files can look like the following, when opened in any php editor, and can have many of these $txt variables:

<?php 
// Dream Portal (c) 2009-2010 Dream Portal Team 
// DreamPortal.english.php; @1.1 

global $scripturl, $context; 

// General Strings 
$txt['forum'] = 'Forum'; 
$txt['dream_portal'] = 'Dream Portal'; 
$txt['dp_core_modules'] = 'Collapse or Expand this Module'; 
$txt['dp_who_forum'] = 'Viewing the forum index of <a href="' . $scripturl . '?action=forum">' . $context['forum_name'] . '</a>.'; 
$txt['dp_who_portal'] = 'Viewing the portal index of <a href="' . $scripturl . '">' . $context['forum_name'] . '</a>.'; 
$txt['dp_who_page'] = 'Viewing the page &quot;<a href="' . $scripturl . '?page=%1$s">%2$s</a>&quot;.'; 
?>

I am using the following function to save the translations:

function langSave($lang, $file) {

    // We just don't get values from the form, they have to exist in the english files to be taken seriously.
    $default = loadLanguageFile('english', $file);

    if ($default['txt']) {
        foreach ($default['txt'] as $key=>$string) {
            if (isset($_REQUEST['txt'.$key]) && str_replace(' ', '', $_REQUEST['txt'.$key]) != '') {
                $data.='$txt[\''.$key.'\'] = \''.str_replace("'", "\'", $_REQUEST['txt'.$key]).'\';'."\n";
            }
        }
    }
    if ($default['helptxt']) {
        foreach ($default['helptxt'] as $key=>$string) {
            if (isset($_REQUEST['helptxt'.$key]) && str_replace(' ', '', $_REQUEST['helptxt'.$key]) != '') {
                $data.='$helptxt[\''.$key.'\'] = \''.str_replace("'", "\'", $_REQUEST['helptxt'.$key]).'\';'."\n";
            }
        }
    }

    if (isset($data)) {
        $codes = array (// '' . $test . '
            '/(\{)(.+?)(\})/',
            '/(\'\' \. \$)(.+?)( \. \')/',
            '/(\' \. \$)(.+?)( \. \'\')/',
            '/(\[\')(.+?)(\'\])/',
            '/(\[)(.+?)(\])/',
        );
        $html = array (
            '\' . \$$2 . \'',
            '\$$2 . \'',
            '\' . \$$2',
            '[$2]',
            '[\'$2\']',
        );
        // Convert the data back to normal.
        $data = preg_replace($codes, $html, $data);

        $data = '<?php'."\n".$data.'?>';
        file_put_contents('./'.$lang.'/'.$file.'.'.$lang.'.php', $data);
    }
    languageHome();
} 

Language function:

function languageHome() {

    $languages = loadLanguageList();

    echo '
    Language List
        <table>';
    foreach ($languages as $language) {
        echo '
            <tr>
                <td>
                    '.$language.'
                </td>
                <td>
                    <a href="index.php?op=langView&lang='.$language.'">View</a>
                </td>
            </tr>';
    }
    echo '
        </table>';

}

I fail to see how gettext will help out. There is no way to update the text catalog without rebooting the server every time. Maybe if someone can create a demo of this for me?

Also, would like it to support UTF-8. The data should be consistent.

So what is wrong with this implementation?? Why use gettext?? How can it be used to improve translations to work for both UTF-8 and non UTF-8 language strings so that it can be translated.?

EDIT: Please note, the files will eventually need to be renamed to: ManageDPModules.[language].php, DreamPortal.[language].php, etc., etc. in order for the translations to work. So, how would catalogs help me in this regard? If you want to see possible END-RESULT Translations, you can download a language package located here and open up the .german.php language files to see what it should look like after the member submits a language on a file by file basis. Noted that some of these packages have UTF-8 strings, and some do not. The filename of the packages let you know this. Would be nice if I can also make it support UTF-8, but it is not a requirement. Please note, I'm not trying to create complete packages here. I just want to create the languagefile.[language].php with all of the translated strings inside of them (which my code already does).

OK, I will provide the ENTIRE index.php file for this so you can see what it does exactly when you do translations. Here is the index.php file for this and you'll need some english language files: DreamPortal.english.php, ManageDPModules.english.php, and DreamHelp.english-utf8.php. Now, in order to see this, you need to upload to a server, index.php, create a few folders where index.php is, call 1 english, and create a folder in there for each additional language you want (I did 2 folders, spanish and french), than upload the 3 language files into the english folder. Run index.php in your browser and you will see it working.

Now, how could I use gettext for catalogs with this SAME approach. I need to enable online translation of files. I need to create PHP files of the translations in the SAME style that the .english.php files are with the same PREFIX that is before .english.php, and I need to change the language within the filename to the same language defined for the folder name. Online translations is the ONLY method available. Translator's need to focus ONLY on translating the strings. They shouldn't be focusing on installing programs, packaging it up, renaming the files, etc. etc.. This makes this process as painless as possible allowing it to be done online. And I know there is a way to do this, and even for UTF-8 support. But I'm using the best method that I know how at the moment. But so many of you are MUCH smarter at this sort of thing than I am, so I ask for help from you guys.

Is there anyone that can show me a better way? An example like the one that I have given you in this question?

I need to allow translations to be done ONLINE from translators, and would also like it to support UTF-8 files, as well as non UTF-8 files. I like the way I have it setup for the link provided above (TRANSLATIONS TEST) so that translator's can just do the translations online and not have to worry about anything else, and it would automatically create the files needed. Which would be the same as the english filename of the language with the folder name (representing the language) after the first . (dot) and it needs to have the extension .php (like it does in the code I am currently using). So basically, I need an adaption of the current index.php to support UTF-8 and non UTF-8 for all or most languages, and was told that using gettext() and catalog files would help with this.

Looking for a modified version of my current index.php to use gettext() in a way that it will support most, if not all, languages and translations. The REGEX I got going on for preg_replace isn't completely satisfactory because it seems to place a forward slash in front of double quotes when saving/submitting the translations. So perhaps an improvement on the preg_replace would be needed also.

I provided a complete example with ACTUAL CODE bytheway. I'd like for someone to alter this example, with the CODE that I provided to USE GETTEXT instead and support UTF-8. Or actually provide a ACTUAL METHOD for me to do this myself with. Not looking for a bunch of links that I can find on my own!

Thank You!

SoLoGHoST
  • 2,673
  • 7
  • 30
  • 51
  • 2
    The suggestion was to start over, since the regex approach is weird enough to require a plea for help. And instead of the PHP gettext extension, you can use INTL or php-gettext or upgradephp gettext.php, the fiddly Zend_Translate API or PhpWikis mo2php gettext variation. – mario Dec 09 '10 at 17:48
  • @mario: I'd totally upvote your response if you had made it an answer instead of a comment. – Till Dec 09 '10 at 19:09
  • Bounty? How do you add a bounty, not seeing that option in here. – SoLoGHoST Dec 09 '10 at 20:02
  • You can only start a bounty after a day. – Martin v. Löwis Dec 09 '10 at 20:10
  • Well, it's been a full day and still unable to start a bounty on this, arggg. – SoLoGHoST Dec 11 '10 at 05:35
  • OK, Bounty set at 200 points. – SoLoGHoST Dec 12 '10 at 06:34
  • Why do you want to support non-UTF8 strings? What does this win you? It only complicates things, leads to inconsistencies and I don't see how it can be done if the input the PHP script gets should be UTF-8 if you use that as your charset. – Rosh Oxymoron Dec 12 '10 at 07:22
  • Well, honestly, their are more files than the 3 that I have allowed for downloading as you can see on my test page. There are UTF-8 and non utf-8 files that need to be created because the platform on which it will run provides the option to the end-user to have UTF-8 or not. So, it need to get strings that are encoded in UTF-8 if the filename has `.[language]-utf8.php` in it. Otherwise, it is not a UTF-8 file and will be named like so: `.[language].php`. For Example: `ManageDPModules.english.php` and `ManageDPModules.english-utf8.php`. When editing strings, it needs to look right. – SoLoGHoST Dec 12 '10 at 07:28
  • Honestly, If it supports either UTF-8 or non UTF-8, I'd be happy with either one of these. Supporting BOTH of these would be a HUGE PLUS though! Basically, like I stated above, you can tell which one needs to be loaded up for UTF-8 and saved in UTF-8 by it's filename. So this should be used to determine whether it should be loaded for UTF-8 or not. Thanks :) – SoLoGHoST Dec 12 '10 at 07:33
  • 2
    So, what your question actually is about is a translation editor? -- Btw, the last paragraph says something about `forward slash in front of double quotes when saving/submitting the translations`. May I assume that these are actually backslashes? Then it would be the classic `magic_quotes` fail. – mario Dec 12 '10 at 15:56
  • Actually, yeah, I believe they are backslashes... Thanks again. – SoLoGHoST Dec 14 '10 at 05:08

2 Answers2

13

Why to re-invent the wheel? Maybe I don't understand but I'm pretty sure that what you're trying to do has been done before.

Just use one of the many-many existing solutions.

  • Visit each of them & filter out the irrelevant projects
  • Learn the differences between the rest and choose the one that suits you the best
  • Continue develop & customize your own needs based on the existing project
  • If you've found a bug or if you think the original project will benefits from your additions contact the author nd inform him

Here are some things to think about, when making your decisions:

  • The language the project uses
  • The ability to extend, add features and customize to your own needs
  • Does it a service? or an open source you can download & use at your own server
  • Does it support translation of plurals?
  • The community & resources available for this project
  • Does it also comes with a web-based UI like you've requested?

Here are some (randomly ordered):

Good luck!

Update: thanks, El Yobo. I've marked this as a community wiki, anyone is welcome to edit or reply with another projects if he find something else.

Dvir Berebi
  • 1,406
  • 14
  • 25
  • +1 for that list. I need to translate our project between English and American some time soon... small differences, but annoying ones, and this will be useful to put down a good foundation that we can later use for more substantial translations. – El Yobo Dec 13 '10 at 04:03
  • Not sure why you are providing me links and stuff. This is not what I asked for bytheway. I provided a complete example with ACTUAL CODE bytheway. I'd like for someone to alter this example, with the CODE that I provided to USE GETTEXT instead and supporting UTF-8 would be a plus. Or, maybe to actually provide a **ACTUAL METHOD** for me to do this myself with. Not a bunch of links that I can find on my own! What a disappointment this Bounty is turning out to be. Unfortunately, it's too late to change my mind about it now :( – SoLoGHoST Dec 13 '10 at 19:09
  • Ok, let me try this again. Needing an example of how, using php's `gettext` function can be applied to the functions and/or code that I am using already. I already spent too many hours on this and don't want to start all over trying to learn a whole new system that I already understand with the code that was made already. – SoLoGHoST Dec 13 '10 at 20:54
  • I don't understand how this was misunderstood as the title of this question basically says it all! – SoLoGHoST Dec 13 '10 at 21:31
  • 1
    The answer is providing you *what you need* rather than *what you asked for*. – El Yobo Dec 14 '10 at 04:31
  • lol, I asked for what I need. I'm not sure how to setup `gettext` into what I have. And I am riddled with more questions asking me things in return. I have been more precise in my question than most people are, so I'm not sure how much more precise it can be. – SoLoGHoST Dec 14 '10 at 05:07
  • You asked for what *you* think you need; dvb thinks you need something else to do what you want. I think it's a valid and useful answer, so I +1'd it. – El Yobo Dec 14 '10 at 23:30
  • 1
    Possibly related links: http://crowdin.net/ http://www.transifex.net/languages/ http://en.flossmanuals.net/opentranslationtools http://sourceforge.net/projects/gpltrans/ – Xuni Jan 01 '11 at 22:48
  • I use GETTEXT and this is the most useful post I have seen for this topic yet, thank you for the list of projects that can EDIT/MODIFY/CREATE GETTEXT .po and .mo files! – JasonDavis Mar 22 '11 at 23:04
6

When you have been recommended to use gettext, it was actually an advise to use a gettext-like translation system. Your current code is complex because of mnemonic text indicies. And the trouble you got into with the regular expressions for editing is caused by fiddling with variables in there. Let me propose some alternative code for transitional purposes.

The beauty of gettext lies in its use of a non-obtrusive API. The function call _() is simple enough to be thoroughly used without adding much syntax or code bloat. It's preferrable to things like getTextTrans('ABBR_TXT_ID'). Using such mnemonic text ids is a widespread fallacy; because in practice there aren't frequent rewordings and _("Raw english original text.") serves the same purpose. However, since you already have mnemonic keys in place, keep them if it's too much to change. This is just a recommendation.

Your real problem is the use of inline PHP expressions to build up the translation target strings. They are why the regular expressions for your translation editor became opaque. Therefore I'd highly recommend to use static strings and provide placeholders. The translation function should be tasked with handling it. (Don't worry about microoptimizing here!) - I would use {$url_xy} PHP/Smarty-style placeholders for example:

$txt['dp_who_forum'] = 'Viewing the forum index
of <a href="{$scripturl}?action=forum">{$forum_name}</a>.'; 

And a translation function that looks up a global placeholder table or params ($context) for replacing:

function __($text, $params=array()) {
    global $txt, $txt_placeholders;
    if (isset($txt[$text])) {
        $text = $txt[$text];
    }
    if (strpos($text, '{$')) {
        $params = array_merge($params, $txt_placeholders);
        $text= preg_replace("/\{\$(\w+)\}/e", "$params['$1']", $text);
    }
    return $text;
}

Optimizable. But this way you can use a static mnemonic->text or english->text set of translation arrays. You only use static strings in your text-translation-editor-form thingy. Those static strings are shown as-is, and your translators edit the english text, but not any of the {$placeholders}.

Hence your code for the translation feature won't need any of the complex regular expressions (in this case they are not useful) to match strings and inline PHP variables. In fact a much simpler include() and var_export() combination can now take its place:

function langSave($lang, $file) {
    $txt = array();
    include($file);   // assuming it contains a simple $txt = array(...

    foreach ($txt as $key=>$string) {
        $txt[$key] = $_REQUEST["txt$key"];
    }
    file_put_contents($file, "<?php\n\$txt =".var_export($txt,1).';?>');
}

Filehandling and whatnot needs to be customized of course. But still this is a simpler approach.

And it would further allow you to transition to one of the gettext variations as backends. Keep your custom wrapper function __() and use e.g. Zend_Translate as backend. This allows you to keep using your .php $txt=array() translation files (or I think so) or move to gettext-style .mo/.po files. The advantage of that being, that there is rich tool support in contrast to homebrew solutions.

Anyway this is how you use the stuff in your main application:

print "<a href='...'>" . __("link_text") . "</a>";
print __("dp_forum_link");   // uses e.g. $txt_placeholder["scripturl"]
print __("dp_param_link", array("scripturl"=>"http://override..."));

Moving from the short_txt keys to english->foreign text translation arrays would be advisable first, but in theory any of the backends from dvbs answer would be applicable. Now you already said that native gettext is not an option for you (for non-fastcgi PHP setups the memory resistance is a drawback). But PHPs native INTL features might be of help to you. In particular http://www.php.net/manual/en/class.messageformatter.php might be more useful than the simple-minded {$var} replacement wrapper I gave you. But I've never used it, and I think Zend_Translate is likely more useful. In particular can any of those backends give you charset independence. Personally I'd just stick to UTF-8, no matter what. But gettext .mo/.po files can each have their own custom charset for example.

mario
  • 144,265
  • 20
  • 237
  • 291
  • Why not use `vsprintf` instead of the smarty style approach; it seems like it would be more simple and better performing. Seems like a sound approach overall. – El Yobo Dec 14 '10 at 04:33
  • @ElYobo: He already uses some sprintf variation, as one of the strings contained `%1$s">%2$s`. I'm not sure about the use cases, so I made up a secondary replacement feature. The more the merrier. – mario Dec 14 '10 at 04:41
  • +1, Thanks for your response. Am interested in using the `vsprintf` function that El Yobo mentioned, is this possible to do passing `$txt` into the array (2nd parameter of vspintf). Would it work better? Cheers :) Just noticed your comment now, so I suppose it might conflict with the `sprintf` than. Anyways, thanks MUCH! Will test this theory out a bit and get back with you on this! – SoLoGHoST Dec 14 '10 at 04:58
  • How would I call this function bytheway? `function __($text, $params=array())` Still a bit confused on this, sorry and thanks for your input again. – SoLoGHoST Dec 14 '10 at 05:03
  • @SoLoGHoST: Not sure. `vsprintf` is mainly a more standard method for using placeholders. You could use it like `vsprintf("English %s test with %s placeholders", array($scripturl, $forumname))` instead of named placeholders. – mario Dec 14 '10 at 05:04
  • @SoLoGHoST: The invocation is just `__('dream_portal')` in your case. Or if you want to supply parameters `__('dp_who_page', array("forum_name"=>"..."))`. But I would avoid the placeholder parameter and just assemble any replacements in the global $txt_placeholder array. – mario Dec 14 '10 at 05:06
  • Thanks, will use the global $txt_placeholder array that you set in motion. So I would do a foreach loop on all $txt variables and call `__($key)` to load up the strings? Where $key = the key of the $txt array. – SoLoGHoST Dec 14 '10 at 05:31
  • @SoLoGHoST: In theory yes. But you could just loop over the global $txt[] itself. In particular for the editor, you wouldn't want to have __() do the placeholder interpolation. – mario Dec 14 '10 at 05:34
  • @SoLoGHoST: You should call `__('key_id')` throughout your application. - But maybe you could give an example of your current function and its practical usage. – mario Dec 14 '10 at 05:36
  • huh? current function and it's usuage. I included the entire code. See links in question, index.php and the languages. What are you asking for an example of exactly? – SoLoGHoST Dec 14 '10 at 05:40
  • Basically the `__($key)` function call will be replacing the `loadLanguageFile` function, am I right? Than I would need to load up the global $txt only once and have it globalized in all functions and than use it in those functions where it calls `loadLanguageFile` and instead do a `foreach` loop with `__($key)` inside of the loop, right? Should I just do an include on the file, instead of passing it with the `eval` function? – SoLoGHoST Dec 14 '10 at 05:43
  • @SoLoGHoST: Did you use some form of `getString()` function till now, or did you access the translation strings directly via `$txt['forum']`? The __() function is supposed to wrap $txt[...] access. The `loadLanguageFile` is compacted to a single include() that sets the global $txt array. – mario Dec 14 '10 at 05:43
  • I am using php's function `eval()` to include the `$txt` variables. – SoLoGHoST Dec 14 '10 at 05:45
  • @SoLoGHoST: Yes, I was recommending that you replace eval() with a single include(). Thus you will have $txt[] available everywhere. You can either keep using $txt['forum'] for static strings or __('forum') for translation strings which might have placeholders. – mario Dec 14 '10 at 05:48
  • But the $txt variables will change depending on the file that gets loaded, so how would that affect this? Because we wouldn't want to load up previously called files and $txt arrays. How can this be wiped, just unset it before setting it I suppose. But would need to be a global in all functions? – SoLoGHoST Dec 14 '10 at 05:56
  • @SoLoGHoST: I was actually talking about your general application. -- But back to the editor part now: Shorten your `loadLanguageFile` to a single include(). If it includes the translation array, it will just set a **local $txt** variable (since there is no `global $txt;` definition in that new loadLanguageFile), which you can safely `return`. Then to display the translation/editor form, just foreach-loop over $txt[] and its keys and text strings. Display as-is to your translator. When the form is submitted, save the new strings back (as your existing code already does). – mario Dec 14 '10 at 06:01
  • Yeah, thanks, just working on the bit to differentiate between the translated strings and the untranslated strings. It will need to include 2 files, the english and the language that it is being translated to, in order to bring up the current translations. Because people should be able to edit their translations as well. Working on it now. Should probably use `array_diff_key`. – SoLoGHoST Dec 14 '10 at 06:18
  • I think that's easy if you use `$txt=array_merge(include(english), include(spanish))`, where obviously the include() should be an indiret call to loadLanguageFile, or use a temporary variable between the two includes. – mario Dec 14 '10 at 06:21
  • WOW, thanks, didn't think of it that way!! Coding it right now. Cheers :) Will keep you up-to-date on the progress. Can you use include() within the array_merge() function though? – SoLoGHoST Dec 14 '10 at 06:28
  • Not as-is, but you could do: `include(english); $txt1=$txt; include(spanish); $txt=array_merge($txt1,$txt);` for example. – mario Dec 14 '10 at 06:33
  • No wait, I don't think that would work, because I am getting 2 sets of strings. The english version and the one they are translating, so it needs to get both of them individually, it can't put them together. So I was right, I need to use `array_diff_key` to get the difference between the keys that were translated and the keys that weren't translated. – SoLoGHoST Dec 14 '10 at 06:47
  • I am getting an error on this line: `$text= preg_replace("/\{\$(\w+)\}/e", "$params['$1']", $text);` says this: `unexpected T_ENCAPSED_AND_WHITESPACE, expecting T_STRING or T_VARIABLE or T_NUM_STRING` – SoLoGHoST Dec 14 '10 at 07:30
  • Ok, nevermind, I changed it to this instead: `$text = preg_replace('/\{\$(\w+)\}/e', '$params["$1"]', $text);` hopefully that is ok for what it does. – SoLoGHoST Dec 14 '10 at 07:41
  • Also, looking at your `var_export` and what it does. While this is a very neat thing to have, it can't be used like this. Because it creates an array. It could use `$txt += array(keys)` instead I suppose. Because there are many many other $txt's which are a global in the software that is running this, and it needs to keep those $txt variables as is. Would be best to just have the key for it. I'm still working on it though. Cheers :) – SoLoGHoST Dec 14 '10 at 07:47
  • If you have multiple text arrays and don't want to distribute them into different files, then the simple `var_export` is insufficient. But you can just use the `foreach` from your initial `saveLang`. Store the lines individually with `$data.='$txt['.var_dump($key).'] = \''.var_export($string)...` or so. – mario Dec 14 '10 at 15:10
  • More importantly: do **NOT** use the `__()` function for the editor loading/saving functions (`langEdit`). You are supposed to display the **raw** $txt strings for translation. When someone translates the texts, they are supposed to leave the `{$var}` placeholders in there (`Spanish spanish {$var} spanish`). – mario Dec 14 '10 at 15:12
  • Thre sole purpose of the `__()` function is to be used within the application logic to display translated strings (`print __('text_output')`). Don't access the `$txt[]` array directly within the remainder of your application. The (global) `$txt[]` array is only to be used for the editing/saving functions. – mario Dec 14 '10 at 15:17
  • Can you help me here, I provided the link to the index.php here => http://acs.graphicsmayhem.com/rc4/index.php?action=dreamFiles;mod=17;id=7 Thanks :) – SoLoGHoST Dec 14 '10 at 15:43
  • I've already seen it. You need to use `` rather than `__($key)`. Likewise for the `__($all_string` should be just `$all_strings[$key]`. – mario Dec 14 '10 at 15:49
  • Ok, which is better to use: `$data .= '$txt[\''.$key.'\'] = \'' . $_REQUEST["txt$key"].'\';'."\n";` OR `$data .= '$txt[\''.$key.'\'] = \'' . var_export($txt[$key]).'\';'."\n";`, `var_dump` is not working for outputting the key of the $txt variable, it outputs a blank string instead. Also, I am unable to get the already translated strings to populate the textarea where they have been translated already. Any clues? – SoLoGHoST Dec 14 '10 at 20:34
  • Keep either `var_export` or use `addslashes` when populating it manually. Better use two arrays ($english_txt and $foreign_txt) and use an `if` instead of an `array_diff` array. – mario Dec 14 '10 at 20:45
  • Ok, one last thing... I see that using your setup it is removing `' . $scripturl . '` and `' . $context['forum_name'] . '` how can I have this replaced with the `{$scripturl}` and `{$context[forum_name]}` and vice versa? I see you have this bit of code in the saveLang function: `$text = preg_replace('/\{\$(\w+)\}/e', '$params["$1"]', $text);` will that automatically save it to the way it needs to be? Also, what is the params array used for again, and how can I use it? Example would be great. Thanks, you definitely deserve the 200 pts for this one. Very much appreciated bro! – SoLoGHoST Dec 14 '10 at 21:23
  • @SoLoGHoST: Appearantly I've not explained it well. This particular `preg_replace()` does **not** belong into the saveLang or loadLanguageFile. The regex is part of the for-application `__()`. You don't use that \__() function **anywhere** in the translation form code. Your translation text editor is only concerned with **static strings**. Some of your static strings contain placeholders `{$scripturl}`, which are to be shown as-is in the text-translation-editor. In your **main application** however you define `$txt_placeholder`, and __() automatically uses it (or said $params). – mario Dec 14 '10 at 21:48
  • It's not in the saveLang or loadLanguageFile function (Note: loadLanguageFile function has long been removed). The preg_replace is located within the __() function. How do I define $txt_placeholder? As a global? What exactly should be placed in the place holder, the `{$script_url}` or the `' . $script_url . '` text. I'm guessing that the first parameter is to use the gettext automatically on all strings. But I'm really trying to grasp the value that the place holder should get. Sorry for being stupid here. – SoLoGHoST Dec 15 '10 at 00:09
  • Ok, I have actually did a ton more editing to it and thing I have everything worked out PERFECT now with it. Can you please take a quick look at this and let me know what you think mario. I need some pointers on exactly how to understand the place holder if you could help me with this last thing please. link => http://acs.graphicsmayhem.com/rc4/index.php?action=dreamFiles;mod=17;id=8 and you can see it working here: http://acs.graphicsmayhem.com/test4/ – SoLoGHoST Dec 15 '10 at 00:39
  • Yes, define it in your main application like: `global $txt_placeholder = array("script_url"=>..., "forum_name"=>...,`. Try to pre-define all possible placeholders globally, so you can avoid the $params parameter of `__(..)`. Basically $txt_placeholder should contain your previous $scripturl and the $context array if workable. – mario Dec 15 '10 at 00:41
  • Actually this looks alright. The translation editor looks pretty cool I must say. There's only one thing that's odd: `str_replace('\"'` should really be `stripslashes()` - it does not remove single quotes. But you should do that in a central location anyway with something like `if(get_magic_quotes_gpc()){$_REQUEST=array_map("stripslashes",$_REQUEST);...}` - see examples in the php manual http://php.net/manual/en/security.magicquotes.disabling.php or stackoverflow http://stackoverflow.com/questions/1997039/antidote-for-magic-quotes-gpc – mario Dec 15 '10 at 00:49
  • One thing that won't work is the remaining placeholder `${context[forum_name]}`, since $txt_placeholder is only a one-level array. But to get back on the topic if you need them at all, I see it's actually just very few strings that need variables in the first place. If so, you could drop the whole placeholder idea, and go back to using `vsprintf`. Your translation strings would then be something like `"Click – mario Dec 15 '10 at 00:53
  • Ah okay, back to the magic_quotes issue. The problem with `stripslashes` and the missing single quotes is actually that you don't use `var_export` anymore. In that case a manual addslashes would be advisable in `.= '$txt[\''.$key.'\'] = \'' . ADDSLASHES($_REQUEST["txt$key"]) . '\';' . "\n";`. Otherwise you **would** somewhen run into problems if any of the static strings contained a single quote or a backslash itself. – mario Dec 15 '10 at 00:58
  • Actually, like you said earlier, I am already using sprintf within some of the $txt variables that could conflict with this. Is there another way? I don't mind defining all of the variables if need be. Not a big deal IMO. So I do need stripslashes after all than... argg. – SoLoGHoST Dec 15 '10 at 01:04
  • I would not rely on magic_quotes. On your next PHP/server upgrade it might get disabled and thus your code breaks. Clean it up centrally, apply addslashes() manually where needed. -- Well okay, if you already use sprintf somewhere else, then this would get messy. Retain the placeholders, and pre-define as many of them as possible. I don't see any better options there. This is the part where all translation/gettext-like systems suck. (Zend_Translate or the PHP MessageFormater APIs would be likewise complicating it.) – mario Dec 15 '10 at 01:10
  • Ok, sure, but how do I use the $place_holder. Can you be a bit more specific. This is a bit confusing for me. What would be the function that I would need to call the __() from in order to get the $place_holder. How would it be set up exactly? More importantly, what is supposed to go in the $place_holder array exactly. Also, looking up the `gettext` functions implies that I need to set a locale and use `bindtextdomain`, etc. etc.. Is this really needed in my case? Ok, so I should do a `!empty(get_magic_quotes_gpc) ? ON : OFF` than? – SoLoGHoST Dec 15 '10 at 01:17
  • Your code does not use gettext at all. You are using raw php array scripts instead of gettext mo/po files. The real gettext function is a single underscore "_", but your wrapper is two underscores "__". No need for `bindtextdomain` or any other of this. – mario Dec 15 '10 at 01:21
  • See the link again http://stackoverflow.com/questions/1997039/antidote-for-magic-quotes-gpc for advise on magic quotes. – mario Dec 15 '10 at 01:22
  • Ok, but is this method of translating strings fine? Well, there is the `__()` and isn't that the shorthand for the gettext function? I'm confused here... – SoLoGHoST Dec 15 '10 at 01:24
  • You do not call `__()` to get any placeholders. The `{$scripturl}` only lives in your static translation strings. Okay? But if you call any text string via `__('text_id')` than that `{$url}` is auto-resolved on the spot. That is all. – mario Dec 15 '10 at 01:24
  • If the two-underscore name looks confusing or ambigious, pick a different. It's also very common to use `t()` instead. `t` being shorthand for "text". – mario Dec 15 '10 at 01:26
  • Ok, so if I call it `function t()` it will automatically load this function up? I thought functions need to be called in order for them to load. Sorry for the millions of questions here. – SoLoGHoST Dec 15 '10 at 01:34
  • Yes, call it `function t()`. Once defined, a function can be used. In hindsight this is also a more exact function name. – mario Dec 15 '10 at 01:37
  • No, keep going with your questions. I think there is a Stackoverflow badge for that. lol – mario Dec 15 '10 at 01:38
  • Ok, so right now the function __() is not being used at all? For some reason I thought it was. Ok. You've got a great sense of humor ;) I wouldn't be surprised if I got the "Badge of Shame" or something...lol. – SoLoGHoST Dec 15 '10 at 01:46
  • Well that's the very point that you haven't answered yet: *How did you access the $txt[] variables till now?*. The point of `t()` is to be used, just *within the main application*, and just there. – mario Dec 15 '10 at 02:02
  • How did I access the $txt[] variables? What do you mean? I was using phps `eval()` function to import it all. Main application? You mean when the $txt variables are being loaded and displayed on the page? Or just prior to it being saved? – SoLoGHoST Dec 15 '10 at 03:57
  • @SoLoGHoST: Yes, main application. How do you display those variables? – mario Dec 15 '10 at 04:43
  • The `langEdit()` function is responsible for displaying the variables on the page when viewing and editing strings. You can see that I am including the english version of the file, than grabbing all `$txt` and `$helptxt` variables that have been loaded by including the file. Here is my most recent index.php file => http://acs.graphicsmayhem.com/rc4/index.php?action=dreamFiles;mod=17;id=9 so you can take a look at the `langEdit` function to see how it loads up the $helptxt and $txt strings. In here should be the call to the function, but not exactly sure where it should be. – SoLoGHoST Dec 15 '10 at 05:25
  • There are 2 ` – SoLoGHoST Dec 15 '10 at 05:29
  • @SoLoGHoST: No it does not. I've already seen your editor part. But is this really all of your application? You are just translating those strings, but never actually use them in any kind of real application? (It seems you don't yet realize that your **main** application is supposed to acccess the text strings differently than the **editor** part.) – mario Dec 15 '10 at 05:37
  • Ohh, if you want to see the actual software program where these strings will be used in, go here => http://dream-portal.net/index.php?action=downloads;area=packs However, we will be releasing Dream Portal 1.1 very soon that addresses a ton of minor bugs and a lot of extra features. **But you have to install SMF 2.0 RC4 first**, located here => http://download.simplemachines.org/ If you want to see languages that have already been translated for Dream Portal, go here => http://dream-portal.net/index.php?action=downloads;area=langpacks Sorry, I misunderstood you. – SoLoGHoST Dec 15 '10 at 06:04
  • But honestly, this is irrelevant. I'm not changing how the main application accesses the $txt variables. The Translation Tools (which is what this is) is only to make translations easier for our Translator's Team at http://dream-portal.net This has nothing to do with the application, just to help them to translate Dream Portal into more languages. The files need to look similar to the language packs located here => http://dream-portal.net/index.php?action=downloads;area=langpacks – SoLoGHoST Dec 15 '10 at 06:09
  • I can package up the files myself, once they are created and translated via this index.php file, this is not a problem. The obstacle that I need to overcome is making it easier for Translators to translate the strings, as opposed to opening up the languages folder from the download of Dream Portal and working from those files instead. Besides, many translator's don't have the time/resources to do anything that is not directly done online! – SoLoGHoST Dec 15 '10 at 06:11
  • This is the information I have been asking for. And it is highly relevant. I've told you around a dozen times that the `t()` function is to be used. By the main application. `Bam bam bam`. – mario Dec 15 '10 at 06:16
  • To clarify, these are language files for the Dream Portal software that don't get tied directly into the software, but rather MUST be installed. During installation, the files get placed into the languages folder of SMF, and SMF handles the rest, with a function called `loadLanguage`. So basically, the index.php file that we've been working on, does not tie into the Dream Portal program at all! It is only responsible for creating the language files, that will than be packaged up and than be installed into SMF so that Dream Portal can use these strings in that language. – SoLoGHoST Dec 15 '10 at 06:18
  • So there is NO WAY to write the strings into the language file(s) with the only difference being the variable? I thought that is why we are using smarty tags, so that we can replace these with the variable name when saving it to the file via langSave function. – SoLoGHoST Dec 15 '10 at 06:19
  • If your main application accesses the global `$txt[]` array directly, then guess what will happen. It will see the raw `{$placeholder}` tokens, which happend to be handled by the `t()` function. This means for you **rewriting main application code**. Fortunately only those that access `$txt['snippets']` which use placeholders into `t('snippets')`. That's why it's called gettext-style translation function. – mario Dec 15 '10 at 06:21
  • So, what needs to be changed? I am wanting to install this on SMF, where $txt and $helptxt variables would conflict with this I believe, since these globals can be used on any function when inserted into SMF. I'm guessing the $txt and $helptxt needs changing to different globals for this? However, including files and assigning the a variable to every $txt/$helptxt might not help us than. – SoLoGHoST Dec 15 '10 at 06:26
  • Ofcourse, we could alter the loadLanguage function a bit in SMF also to add this `t() function` into it somehow. Or, instead of installing it within SMF, I can just run it in a different directory on the server entirely so that it doesn't affect SMF at all. And translators can go there and translate the strings for these files. That would probably be the best approach. I'm willing to do it that way, but how and when do I use the t() function? – SoLoGHoST Dec 15 '10 at 06:30
  • This is rich. You should have elaborated on that usage pattern of yours earlier, like last week. Anyway, you're still not explaining it fully, but I assume "installing" means copying the files into "SMF???" as-is. And I'm now assuming all those variables in your text strings aren't actually runtime variables (which would also have been a relevant information). In that case, you should add an `exportLang` function, which packages up the strings into final static translation array files. This is the place where you'd use the `t()` function then. (In lieu of main, which is inaccessible??) – mario Dec 15 '10 at 06:32
  • Well, okay. Still options then. Either do the export feature, or hack the `t()` function into SMF as outlined before. I don't see how a global $txt or $helptxt is conflicting with anything there. Just change any occourence of `$txt['dp_who_page']` into `t('dp_who_page')`. That's it. – mario Dec 15 '10 at 06:34
  • Ok, this is Not a problem, I will copy index.php and the files into a completely different directory structure and make it accessable to translators. So this won't be installed or run from within SMF or Dream Portal until it is packaged up MANUALLY by me. So how would I call t() if doing it this way than? – SoLoGHoST Dec 15 '10 at 06:35
  • Also, how are the $params supposed to be used? – SoLoGHoST Dec 15 '10 at 06:38
  • If you want to do the `exportLang` feature, you're on your own from here. It's basically the same as `langSave`, except that you loop over the existing files and keys, and write `var_export(t($key))` instead of `$_REQUEST[...`, and use different filenames of course. Don't use the $params then. Pre-define $txt_placeholders with static values. (But I still don't understand how '$scripturl' can be pre-defined on a development system without knowing the target installation, hence I was advocating for runtime replacement via `t()` instead). – mario Dec 15 '10 at 06:41
  • Well, ok, but can you atleast explain to me what this preg_replace is doing exactly? `$text = preg_replace('/\{\$(\w+)\}/e', '$params["$1"]', $text);` – SoLoGHoST Dec 15 '10 at 06:50
  • It's replacing occourences of `...{$var}...` with the corresponding entry from `$txt_placeholders/$params` (line before: merging two arrays). – mario Dec 15 '10 at 06:54
  • Ok, I know how to fix the situation... TADA. I use a script that replaces all instances of $txt and $helptxt to something like: $txt_dp_lang and $helptxt_dp_lang, than I use these as globals, not $txt or $helptxt, than when each file is 100% complete, I convert over all $txt_dp_lang and $helptxt_dp_lang text in each file over to $txt and $helptxt, than I lock the file. I still don't know how to use the placeholder. Can you give me an example please? – SoLoGHoST Dec 16 '10 at 19:52
  • Sounds overkill. You should read up on global variables. They are only global in the global scope, unless explicitly imported. -- Don't know another way to explain placeholders. – mario Dec 16 '10 at 20:22
  • Well, if you know anything about SMF, there are many $txt and $helptxt variables automatically loaded on every page load. So using these variables to add the Translation Tools to SMF would not work. But doing it this way would. So if I wanted to change all `$scripturl` and `$context['blah']` to `{$scripturl}` and `{$context[blah]}` I would do this? `t($text, array('$scripturl' => '{$scripturl}', '$context[\'blah\']' => {$context[blah]}));` Can you help me with a regex to change it back to `$scripturl` and `$context['blah']` when saving it? – SoLoGHoST Dec 16 '10 at 22:54
  • You could start by helping me to understand what is happening in the function t() which has a global `$txt_placeholders` in it and than the 2nd parameter is an array of `params()`. So if I want all `{$scripturl}` to save in the file as: `' . $scripturl . '` when saving, how can I do this? Which one goes in $txt_placeholder global and which one goes in the 2nd parameter of the t() function? Here is how I am using it: `t(stripslashes($_REQUEST["txt$key"]), array('{$scripturl}' => '\' . $scripturl . \'')) ` but this doesn't work at all. – SoLoGHoST Dec 17 '10 at 00:17
  • Ok, I figured it out. Just using `'scripturl'` instead. Also, I need it to support keys also like this: `context[forum_name]`, how can I have it support this as well? – SoLoGHoST Dec 17 '10 at 00:34
  • Might not be using gettext exactly, but I thank you for your time and help with this. Very Much appreciated, and definitely showed me a better way to do this. Cheers :) – SoLoGHoST Dec 17 '10 at 00:49
  • The idea behind the `t()` function was that you can move on to gettext in the future. Going back to string monkey patching was never intended, and injecting back `' . $scripturl . '` is a kludge at best. (I don't understand why you're not just using t() in the main app). If you want to support `context[forum_name]` without renaming, change the regex as follows: `preg_replace('/\{\$([\w\\[\\]]+)\}/e', '$params["$1"]', $text);` - cannot support this; not reliable. – mario Dec 17 '10 at 01:14
  • what's wrong with this `$text = preg_replace('/\{\$([\w\"\[\]]+)\}/e', '$params["$1"]', $text);`? Anything? The only difference is the `"` in there. – SoLoGHoST Dec 17 '10 at 02:23
  • It's identical. You don't need the `"` in your case actually, but it doesn't hurt either. – mario Dec 17 '10 at 02:25
  • Wait, how do I grab variables that are defined in the $txt arrays? I'm using an include on the file, so how can I replace variables like: `$scripturl` and `$context['forum_name']` that are placed in here using include? – SoLoGHoST Dec 17 '10 at 03:11
  • Example here: `$txt['dp_who_forum'] = 'Schaut den Forumindex von ' . $context['forum_name'] . ' an.';` – SoLoGHoST Dec 17 '10 at 03:12
  • How do I replace `' . $scripturl . '` with {$scripturl} for the output of it? – SoLoGHoST Dec 17 '10 at 03:13
  • Well, that seems from your original approach. Which is completely non-workable. – mario Dec 17 '10 at 03:13
  • Go here: http://acs.graphicsmayhem.com/test4/index.php?op=editLang&file=DreamPortal&lang=german Look at **dp_who_forum** well you see that the {$scripturl} and the {$context[forum_name]} is not in there. It should read this: `Schaut den Forumindex von {$context[forum_name]} an.` instead of this: `Viewing the forum index of .` What are possibilities of getting this in there, where it belongs? – SoLoGHoST Dec 17 '10 at 03:18
  • I need to inject the variables at the given spot, but as `{$scripturl}` and `{$context[forum_name]}` is this possible? – SoLoGHoST Dec 17 '10 at 03:24
  • There is not a sufficiently heuristic parser to inject missing tags there. I'm making a wild assumption that you actually `include()` the exported files again. If so, then PHP will replace `...' . $undef_scripturl . '...` with nothing. Solution: don't read in the exported files, use the ones with the placeholders for the text editor. – mario Dec 17 '10 at 03:37
  • So I use the global $txt_placeholders for this right? Does the function you created automatically check for this. I see an array_merge being done in here for the $txt_placeholders, but it seems that it is acting the same way as the $params array (2nd parameter) for the t() function. So basically, seems that $txt_placeholders does the same as $params. – SoLoGHoST Dec 17 '10 at 04:33