I'm trying to create a backend for translations edit. Strictly speaking I want to let users edit the array in resources/lang/[en|it|es|pt]/something.php
Before start writing spaghetti code with fread/fwrite I would like to know if there's some Laravel way to do it.
You may want to use some package for this task. I'd use this one:
https://github.com/barryvdh/laravel-translation-manager
This is a package to manage Laravel translation files. It does not replace the Translation system, only import/export the php files to a database and make them editable through a webinterface.
I found a solution according to what inside the class proposed by Alexey Mezenin, that use var_export like my first thought. I didn't want to install an entire system to manage my translations, since I'm writing my own backend. My solution is the following (and works like a charm :)
public function saveTranslations(Request $request, $key) {
$locales = LaravelLocalization::getSupportedLocales();
foreach ($locales as $l => $lang) {
${"array_$l"} = Lang::get('recipes', [], $l);
${"array_$l"}[$key] = $request->$l;
uksort(${"array_$l"}, "strnatcasecmp");
$path = \App::langPath() . '/' . $l . '/recipes.php';
$output = "<?php\n\nreturn " . var_export(${"array_$l"}, true) . ";\n";
$f = new Filesystem();
$f->put($path, $output);
}
return redirect(LaravelLocalization::getCurrentLocale() . '/admin/translations');
}
It works essentially like laravel-translation-manager (https://github.com/barryvdh/laravel-translation-manager/blob/master/src/Manager.php#L140), but with a custom logic.
Localization is managed through mcamara/laravel-localization package.
Related
I am trying to loop through all the php files listed in an array called $articleContents and extract the variables $articleTitle and $heroImage from each.
So far I have the following code:
$articleContents = array("article1.php", "article2.php"); // array of all file names
$articleInfo = [];
$size = count($articleContents);
for ($x = 0; $x <= $size; $x++) {
ob_start();
if (require_once('../articles/'.$articleContents[$x])) {
ob_end_clean();
$entry = array($articleContents[$x],$articleTitle,$heroImage);
array_push($articlesInfo, $entry);
}
The problem is, the php files visited in the loop have html, and I can't keep it from executing. I would like to get variables from each of these files without executing the html inside each one.
Also, the variables $articleTitle and $heroImage also exist at the top of the php file I'm working in, so I need to make sure the script knows I'm calling the variables in the external file and not the current one.
If this is not possible, can you please recommend an alternative method?
Thanks!
Don't do this.
Your PHP scripts should be for your application, not for your data. For your data, if you want to keep it file-based, use a separate file.
There are plenty of formats to choose from. JSON is quite popular. You can use PHP's built-in serialization as well, which has support for more PHP-native types but is not as portable to other frameworks.
A little hacky but seems to works:
$result = eval(
'return (function() {?>' .
file_get_contents('your_article.php') .
'return [\'articleTitle\' => $articleTitle, \'heroImage\' => $heroImage];})();'
);
Where your_article.php is something like:
<?php
$articleTitle = 'hola';
$heroImage = 'como te va';
The values are returned in the $result array.
Explanation:
Build a string of php code where the code in your article scripts are wrapped inside a function that returns an array with the values you want.
function() {
//code of your article.php
return ['articleTitle' => $articleTitle, 'heroImage' => $heroImage];
}
Maybe you must do some adaptations to the strings due <?php ?> tags placements.
Anyway, this stuff is ugly. I'm very sure that it can be refactored in some way.
Your problem (probably) comes down to using parentheses with require. See the example and note here.
Instead, format your code like this
$articlesInfo = []; // watch your spelling here
foreach ($articleContents as $file) {
ob_start();
if (require '../articles/' . $file) { // note, no parentheses around the path
$articlesInfo[] = [
$file,
$articleTitle,
$heroImage
];
}
ob_end_clean();
}
Update: I've tested this and it works just fine.
I have done integration with Quick-Books online using quick-books sdk from this link: https://github.com/consolibyte/quickbooks-php.
Everything works perfectly except one issue.
The issue is, when I retrieve Items from quick-books, it returns "Non Inventory" items as "service".
I have read different topics which state that I will have to shift to minor version 4 to resolve the issue.
But I can't find a way of how I can make my current SDK to use minor version 4 or above.
Any help will be appreciated.
For those who have similar issue, I have found a workaround and sharing it for others to benefit if they want:
open the file quickbook_sdk\QuickBooks\IPP\Service.php
Find the following function protected function _query($Context, $realmID, $query)
Replace the following code
$return = $IPP->IDS($Context, $realmID, null, QuickBooks_IPP_IDS::OPTYPE_QUERY, urlencode($query));
With
$query = urlencode($query);
$query .= "&minorversion=4";
$return = $IPP->IDS($Context, $realmID, null, QuickBooks_IPP_IDS::OPTYPE_QUERY, $query);
Note: I was querying Items, so this workaround may only be beneficial in case of getting data through query.
UPDATE:
if you want to add / update items to QuickBooks online with item type NonInventory, You need to modify the following code in your quickbook_sdk/QuickBooks/IPP.php file.
Find function named function _IDS_v3 and inside that function find the following condition
if ($optype == QuickBooks_IPP_IDS::OPTYPE_ADD or $optype == QuickBooks_IPP_IDS::OPTYPE_MOD)
{
$post = true;
$url = $this->baseURL() . '/company/' . $realm . '/' . strtolower($resource);
$xml = $xml_or_query;
}
Replace it with
if ($optype == QuickBooks_IPP_IDS::OPTYPE_ADD or $optype == QuickBooks_IPP_IDS::OPTYPE_MOD)
{
$post = true;
$url = $this->baseURL() . '/company/' . $realm . '/' . strtolower($resource);
$xml = $xml_or_query;
$url .= "?minorversion=4"; // this is the only addition
}
For those who use the official php sdk from Intuit, in the root folder you'll find the file sdk.config. Edit <minorVersion>3</minorVersion>.
If you go to the official PHP SDK:
https://github.com/intuit/QuickBooks-V3-PHP-SDK
You will see that you can use:
$dataService->setMinorVersion("4");
to set the minor version you want to use before making the HTTP call.
I am developing a php website that needs to be multilingual.
For this reason, I implemented a translation function which has the following header:
function t($string, $replace_pairs = array(), $language = NULL)
Basically, this function is called like this in multiples files of my project:
echo '<p>' . t('Hello world!') . '</p>';
$hello_String = t("Hello #name!", array('#name'=>$username));
I haven't generated the translation strings yet and I would like to generate multiple translation file automatically (one for each language).
What I am looking for is a bash program (or a single command, using grep for example) that would look for every call to this t() function and generate a php file with the following structure:
<?php
/* Translation file "fr.php" */
$strings['fr']['Hello world!'] = '';
$strings['fr']['Hello #name!'] = '';
Has anyone ever encountered this situation and could help me with this ?
Thank you very much.
Kind regards,
Matthieu
Yes, you're not exactly the first to come across this. :)
You can use the venerable gettext system for this, you don't need to invent your own functions. Then you'd get to use xgettext, which is a command line utility to extract strings using the _() function.
If you want to roll your own system for whatever reason, your best bet is to write a PHP script which uses token_get_all to tokenize the source, then go through the tokens and look for T_FUNCTIONs with the value t.
No need to reinvent the wheel
Drupal uses the same t() function for its localization and the potx module is your friend.
If you don't already have, or want to install, a drupal instance you can look at the potx.inc file and reuse it in your script.
Here is the complete API documentation for the translation template extractor.
Try this script http://pastie.org/4568713
Usage:
php script.php ./proj-directory lang1 lang2 lang3
This creates lang1.php, lang2.php, lang3.php files in ./lang directory
You need two functions:
1- scan directories for php files. like this
2- match your t function, grep string and generate the language file. like
function genLang($file) {
$content = file_get_contents($file);
preg_match(...);
foreach(...){
echo(...);
}
}
Yii framework also uses same functionality,
see their MessageCommand class
https://github.com/yiisoft/yii/blob/master/framework/cli/commands/MessageCommand.php#L125
What you need is a (very simple) "template system", but there are two instances of templating in your problem.
Transform "Hello $X!" into "Hello Jonh!" or "Hello Maria!", setting $X. (PHP do this for you in string declarations).
Select the adequate template: "Hello $X!" for english, "¡Hola $X!" for spanish.
The item 1 is the more simple, but the algorithm order is 2,1 (item 2 them item 1).
For this simple task you not need a regular expression (to reinvent the "string with place-holder" of PHP).
Illustrating
For item 1, the simplest way is to declare a specialized function to say "Hello",
// for any PHP version.
function template1($name) { return "<p>Hello $name!</p>";}
print template1("Maria");
For item 2 you need a generalization, that PHP do also for you, by a closure,
header('Content-Type: text/html; charset=utf-8'); // only for remember UTF8.
// for PHP 5.3+. Use
function generalTemplate1($K) {
// $K was a literal constant, now is a customized content.
return function($name) use ($K) {return "<p>$K $name!</p>"; };
}
// Configuring template1 (T1) for each language:
$T1_en = generalTemplate1('Hello'); // english template
$T1_es = generalTemplate1('¡Hola'); // spanish template
// using the T1 multilingual
print $T1_en('Jonh'); // Hello Jonh!
print $T1_es('Maria'); // ¡Hola Maria!
For more templates, use generalTemplate2(), generalTemplate3(), etc.; $T2_en, $T2_es, $T2_fr, $T3_en, $T3_es, etc.
Solution
Now, for pratical use, you like to use arrays... Well, there are a datastructure problem,
and more 1 level of generalization. The cost is variable-name parser for place-holders. I used simple regular expression with preg_replace_callback().
function expandMultilangTemplate($T,$K,$lang,$X) {
// string $T is a template, a HTML structure with $K and $X placeholders.
// array $K is a specific language constants for the template.
// array $lang is the language, a standard 2-letter code. "en", "fr", etc.
// array $X is a set of name-value (compatible with $T placeholders).
// Parsing steps:
$T = str_replace('{#K}',$K[$lang],$T); // STEP-1: expand K into T with lang.
// STEP-2: expand X into T
global $_expMultTpl_X; // need to be global for old PHP versions
$_expMultTpl_X = $X;
$T = preg_replace_callback(
'/#([a-z]+)/',
create_function(
'$m',
'global $_expMultTpl_X;
return array_key_exists($m[1],$_expMultTpl_X)?
$_expMultTpl_X[$m[1]]:
"";
'
),
$T
);
return $T;
}
// CONFIGURING YOUR TEMPLATE AND LANGUAGES:
$T = "<p>{#K} #name#surname!</p>";
$K = array('en'=>'Hello','es'=>'¡Hola');
// take care with things like "!", that is generic, and "¡" that is not.
// USING!
print expandMultilangTemplate(
$T, $K, 'en', array('name'=>'Jonh', 'surname'=>' Smith') );
print expandMultilangTemplate($T, $K, 'es', array('name'=>'Maria'));
I tested this script with PHP5, but it runs with older (PHP 4.0.7+).
About "multilingual files": if your translations are into files, you can use somthing like
$K = getTranslation('translationFile.txt');
function getTranslation($file,$sep='|') {
$K = array();
foreach (file($file) as $line) {
list($lang,$words) = explode($sep,$line);
$K[$lang]=$words;
}
}
and a file as
en|Hello
es|¡Hola
Simplest with PHP 5.3
If you using PHP 5.3+, there are a simple and elegant way to express this "simplest multilingual template system",
function expandMultilangTemplate($T,$K,$lang,$X) {
$T = str_replace('{#K}',$K[$lang],$T);
$T = preg_replace_callback(
'/#([a-z]+)/',
function($m,$X=NULL) use ($X) {
return array_key_exists($m[1],$X)? $X[$m[1]]: '';
},
$T
);
return $T;
}
first of all, let me tell ya, that I'm from Germany. So my English will not be very well. Please forgive me. =P
I'm about to develop a multilingual website with PHP5. In order to seperate the presentation layer from the business logic, I'm using the SmartyTemplateEngine (v3.0.8). To make it multilingual I had to edit this SmartyPlugin sometimes. But finally it is working for me. I'm using it that way:
{lang}language.string{/lang}.
In the language file I have:
language.string = <![CDATA[Hello world!]]> (So it says Hello world!)
Works fine. But I want to expend the script a bit further. I want to pass a variable to the language string. Something like this:
{lang s=$userName}language.string{/lang}
In the language file I want to have:
language.string = <![CDATA[Hello %s!]]> (So it says Hello username!)
I tried to find my solution with Google, but I didn't find something good. As far as I'm not a professional in PHP, I'm not capable of editing it myself. I hope somebody can help me with that. It is bothering me quite a while...
Greets,
Basti
Well, I'm not using this plugin, but had to do the same thing. I figuered out that for my needs the following was the best solution:
In an XML file I define my strings (this example is xml/en/content.xml):
<translations>
<translation id="hello_world"><![CDATA[Hello ##username##!]]></translation>
<translation id="how_are_you"><![CDATA[How are you?]]></translation>
</translations>
In my Localizer class I initialize these translations and save them in an array. The translate function gets the ID string from smarty, searches for the id in its translations and for any ##string## text. These ##...## will be replaced with variables already assigned to smarty.
class Localizer {
private static $translations = array();
public static function init($language) {
$temp_content = simplexml_load_file('xml/' . $language . '/content.xml');
foreach ($temp_content as $key => $value){
self::$translations[(string)$value['id']] = (string)$value;
}
}
public static function translate($params, $name, $smarty) {
$translation = '';
if( ! is_null($name) && array_key_exists($name, self::$translations)) {
// get variables in translation text
$translation = self::$translations[$name];
preg_match_all('/##([^#]+)##/i', $translation, $vars, PREG_SET_ORDER);
// replace with assigned smarty values
foreach($vars as $var) {
$translation = str_replace($var[0], $smarty->getTemplateVars($var[1]), $translation);
}
}
return $translation;
}
}
Now you have to tell smarty which function it should use. This could be your index.php:
include('Localizer.class.php');
Localizer::init('en');
$smarty->registerPlugin('block', 'translate', array('Localizer', 'translate'), true);
To use the translations, first, I assign the username:
$smarty->assign('username', $username);
In the template file:
{translate}hello_world{/translate}
Hope this helps, greetings from austria :)
I'm creating an open-source cms and was just wondering that which is the best way to add localizations? I already decided to have them in files similar to lang.en.php. I would assume arrays, but in which form?
$lang['xyz'] = "Text goes here!";
$lang['Text goes here!'] = "Translated text!";
Or should I create my custom parser and add localizations to a file, like this:
"Text goes here!" = "Translated text!";
And then just parse it.
What would you suggest? I tried to search but no results for me.
Martti Laine
I know the Gettext library for Desktop applications does something similar to your custom parser. Gettext has a module in PHP, but I'm not sure if it's installed in most PHP installations by default.
Basically, you would write every string with it with a function name tr("How are you?"). Then create a function to translate it:
include('lang.es.php');
function tr($txt) {
global $tr;
if(array_key_exists($txt,$tr)) {
return $tr($txt);
}
return $txt;
}
And in lang.es.php, have:
$tr = array();
$tr["How are you?"] = "¿Como Estas?";
You would probably want to do printf(tr("How are you, %s?"), $name); for variables, or proper nouns that should not be translated.
I think you should use the Joomla way. Language files must be in ini extension:
FOO=translation
BAR=translation2
then you parse the file with parse_ini_file function and get the translation array:
$dictionary=parse_ini_file("english.ini");
function translate($text)
{
global $dictionary;
if(isset($dictionary[strtoupper($text)])) return $dictionary[strtoupper($text)];
else return $text;
}
It's not as simple as you think it is, do you really need hundreds of rows in an array in order to translate I deleted 45 comments, or I deleted 192 comments? etc.
It would be very helpful if you could call a translate function with: translate('I deleted %d comments', $number);
<?php
$dict = parse_ini_file('lang.ini');
function translate($text){
global $dict;
$args = func_get_args();
if(isset($dict[$text])){
// I am not sure how to convert %d in $args[.], maybe someone else could provide a regular expression for this.
} else {
return $text;
}
}
?>
How will you manage plural form ?
Some languages have very tricky plural rules : example here
In Polish we use e.g. plik (file) this
way:
1 plik
2,3,4 pliki
5-21 pliko'w
22-24 pliki
25-31 pliko'w
For this reason, I suggest you to use gettext because everything has been done for you.