52
0
plg_extension_componentbuil.../componentbuilderlanguagepackaging.php

730 lines
20 KiB
PHP
Raw Normal View History

2021-10-14 14:32:09 +00:00
<?php
2021-12-03 02:46:24 +00:00
/**
* @package Joomla.Component.Builder
*
* @created 30th April, 2015
* @author Llewellyn van der Merwe <https://dev.vdm.io>
2022-08-20 16:38:51 +00:00
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
2021-12-03 02:46:24 +00:00
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
2021-10-14 14:32:09 +00:00
*/
// No direct access to this file
defined('_JEXEC') or die('Restricted access');
use Joomla\CMS\Application\CMSApplication;
2023-10-18 07:28:04 +00:00
use Joomla\CMS\Plugin\CMSPlugin;
use VDM\Joomla\Utilities\JsonHelper;
use VDM\Joomla\Utilities\ArrayHelper;
2024-03-02 20:10:30 +00:00
use VDM\Joomla\Utilities\StringHelper;
use VDM\Joomla\Utilities\GetHelper;
2021-10-14 14:32:09 +00:00
2023-02-05 21:57:58 +00:00
JLoader::register('ComponentbuilderHelper', JPATH_ADMINISTRATOR . '/components/com_componentbuilder/helpers/componentbuilder.php');
use VDM\Joomla\Componentbuilder\Compiler\Factory as CFactory;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Placefix;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Indent;
2021-10-14 14:32:09 +00:00
/**
* Extension - Componentbuilder Language Packaging plugin.
*
* @package ComponentbuilderLanguagePackaging
2024-03-02 20:10:30 +00:00
* @since 1.2.0
2021-10-14 14:32:09 +00:00
*/
class PlgExtensionComponentbuilderLanguagePackaging extends CMSPlugin
{
/**
* Affects constructor behavior. If true, language files will be loaded automatically.
*
* @var boolean
* @since 1.0.0
*/
protected $autoloadLanguage = true;
/**
* Database object
*
* @var DatabaseDriver
* @since 1.0.0
*/
protected $db;
/**
* Application object
*
* @var CMSApplication
* @since 1.0.0
*/
protected $app;
/**
* The percentage before a language can be added
*
* @var int
* @since 1.0.0
*/
protected $percentageLanguageAdd;
/**
* The percentage before a language can be added
*
* @var int
* @since 1.0.0
*/
protected $percentageLanguageAddOveride = 200;
/**
* The languages names
*
* @var array
* @since 1.0.0
*/
2024-03-02 20:10:30 +00:00
protected $languageNames = [];
2021-10-14 14:32:09 +00:00
/**
* The language building tracker
*
* @var array
* @since 1.0.0
*/
2024-03-02 20:10:30 +00:00
protected $languageTracker = [];
2021-10-14 14:32:09 +00:00
/**
* The should the site folder be removed
*
* @var bool
* @since 1.0.0
*/
protected $removeSiteFolder;
/**
* The should the site folder be removed
*
* @var bool
* @since 1.0.0
*/
protected $removeSiteEditFolder;
/**
* The component path
*
* @var string
* @since 1.0.0
*/
protected $componentPath;
/**
* The compiler path
*
* @var string
* @since 1.0.0
*/
protected $compilerPath;
/**
* The temporal path
*
* @var string
* @since 1.0.0
*/
protected $tempPath;
/**
* The joomla version
*
* @var string
* @since 1.0.0
*/
protected $joomlaVersion;
/**
* The component version
*
* @var string
* @since 1.0.0
*/
protected $component_version;
/**
* The component name
*
* @var string
* @since 1.0.0
*/
protected $componentCodeName;
/**
* The file content static values
*
* @var array
* @since 1.0.0
*/
protected $fileContentStatic;
/*
* The line numbers Switch
*
* @var boolean
* @since 1.0.0
*/
protected $debugLinenr = false;
/**
* The Active Components
*
* @var array
* @since 1.0.0
*/
2024-03-02 20:10:30 +00:00
protected $activeComponents = [];
2021-10-14 14:32:09 +00:00
/**
* The Active Components Names
*
* @var array
* @since 1.0.0
*/
2024-03-02 20:10:30 +00:00
protected $activeComponentsNames = [];
2021-10-14 14:32:09 +00:00
/**
* The Languages
*
* @var array
* @since 1.0.0
*/
2024-03-02 20:10:30 +00:00
protected $languages = [];
2021-10-14 14:32:09 +00:00
/**
* The Language build details
*
* @var array
* @since 1.0.0
*/
2024-03-02 20:10:30 +00:00
protected $buildDetails = [];
2021-10-14 14:32:09 +00:00
/**
* The Excluded Languages
*
* @var array
* @since 1.0.0
*/
2024-03-02 20:10:30 +00:00
protected $excludedLang = [];
2021-10-14 14:32:09 +00:00
/**
* The Active Language
*
* @var string
* @since 1.0.0
*/
protected $langTag;
/**
* Event Triggered in the compiler [on Before Model Component Data]
*
* @return void
*
* @since 1.0
*/
2024-03-02 20:10:30 +00:00
public function jcb_ce_onBeforeModelComponentData(&$component)
2021-10-14 14:32:09 +00:00
{
// add the privacy
2023-10-18 07:28:04 +00:00
$component->params = (isset($component->params) && JsonHelper::check($component->params)) ? json_decode($component->params, true) : $component->params;
if (ArrayHelper::check($component->params) && isset($component->params['language_options']) &&
2021-10-14 14:32:09 +00:00
isset($component->params['language_options']['activate']) && $component->params['language_options']['activate'] == 1)
{
// load the admin component details
2024-03-02 20:10:30 +00:00
$this->activeComponents[$component->id] = CFactory::_('Config')->component_context;
2023-10-18 07:28:04 +00:00
$this->activeComponentsNames[$component->id] = StringHelper::safe($component->name_code);
2021-10-14 14:32:09 +00:00
$this->activeComponentsRealNames[$component->id] = $component->name;
// add excluded list of languages
if (isset($component->params['language_options']['languages']))
{
$this->excludedLang[$component->id] = $component->params['language_options']['languages'];
}
else
{
$this->excludedLang[$component->id] = array();
}
// now set the component add languages if we should use local (2)
if (isset($component->params['language_options']['use_percentagelanguageadd']) && $component->params['language_options']['use_percentagelanguageadd'] == 2)
{
$this->percentageLanguageAddOveride = $component->params['language_options']['percentagelanguageadd'];
}
}
}
/**
* Event Triggered in the compiler [on After Get]
*
* @return void
*
* @since 1.0
*/
2024-03-02 20:10:30 +00:00
public function jcb_ce_onAfterGet()
2021-10-14 14:32:09 +00:00
{
// get component id
2024-03-02 20:10:30 +00:00
$id = (int) CFactory::_('Config')->component_id;
2021-10-14 14:32:09 +00:00
// check if there is active
2023-10-18 07:28:04 +00:00
if (ArrayHelper::check($this->activeComponents)
2024-03-02 20:10:30 +00:00
&& isset($this->activeComponents[$id]) && $this->percentageLanguageAddOveride != 200)
2021-10-14 14:32:09 +00:00
{
2024-03-02 20:10:30 +00:00
CFactory::_('Config')->set('percentage_language_add', $this->percentageLanguageAddOveride);
2021-10-14 14:32:09 +00:00
}
}
/**
* Event Triggered in the compiler [on Before Set Lang File Data]
*
* @return void
*
* @since 1.0
*/
2024-03-02 20:10:30 +00:00
public function jcb_ce_onBeforeSetLangFileData()
2021-10-14 14:32:09 +00:00
{
// lets map some arrays to the plugin for later use
2023-02-05 21:57:58 +00:00
$this->compilerPath = CFactory::_('Config')->compiler_path;
$this->tempPath = CFactory::_('Config')->tmp_path;
$this->langTag = CFactory::_('Config')->lang_tag;
$this->debugLinenr = CFactory::_('Config')->get('debug_line_nr', false);
$this->component_version = CFactory::_('Config')->get('component_version', '1.0.0');
$this->joomlaVersion = CFactory::_('Config')->joomla_version;
2024-03-02 20:10:30 +00:00
$this->percentageLanguageAdd = CFactory::_('Config')->percentage_language_add;
2023-02-05 21:57:58 +00:00
$this->removeSiteFolder = CFactory::_('Config')->remove_site_folder;
$this->removeSiteEditFolder = CFactory::_('Config')->remove_site_edit_folder;
$this->componentPath = CFactory::_('Utilities.Paths')->component_path;
$this->componentCodeName = CFactory::_('Config')->component_code_name;
2021-10-14 14:32:09 +00:00
}
/**
* Event Triggered in the compiler [on Before Build Plugin Lang Files]
*
* @return void
*
* @since 1.0
*/
2024-03-02 20:10:30 +00:00
public function jcb_ce_onBeforeBuildPluginLangFiles(&$plugin, &$languages)
2021-10-14 14:32:09 +00:00
{
// get component id
2024-03-02 20:10:30 +00:00
$id = (int) CFactory::_('Config')->component_id;
2021-10-14 14:32:09 +00:00
// check if there is active
2023-10-18 07:28:04 +00:00
if (ArrayHelper::check($this->activeComponents) && isset($this->activeComponents[$id]))
2021-10-14 14:32:09 +00:00
{
// set file name
$file_name = 'plg_' . strtolower($plugin->group) . '_' . strtolower($plugin->code_name);
// extrude the languages that should not remain in the plugin
2024-03-02 20:10:30 +00:00
$this->extrudeLanguages($id, $languages, CFactory::_('Config')->lang_tag, $file_name, 'admin');
2021-10-14 14:32:09 +00:00
}
}
/**
* Event Triggered in the compiler [on Before Build Module Lang Files]
*
* @return void
*
* @since 1.0
*/
2024-03-02 20:10:30 +00:00
public function jcb_ce_onBeforeBuildModuleLangFiles(&$module, &$languages)
2021-10-14 14:32:09 +00:00
{
// get component id
2024-03-02 20:10:30 +00:00
$id = (int) CFactory::_('Config')->component_id;
2021-10-14 14:32:09 +00:00
// check if there is active
2023-10-18 07:28:04 +00:00
if (ArrayHelper::check($this->activeComponents) && isset($this->activeComponents[$id]))
2021-10-14 14:32:09 +00:00
{
// extrude the languages that should not remain in the module
2024-03-02 20:10:30 +00:00
$this->extrudeLanguages($id, $languages, CFactory::_('Config')->lang_tag, $module->file_name, $module->target_client);
2021-10-14 14:32:09 +00:00
}
}
/**
* Event Triggered in the compiler [on Before Build All Lang Files]
*
* @return void
*
* @since 1.0
*/
2024-03-02 20:10:30 +00:00
public function jcb_ce_onBeforeBuildAllLangFiles(&$languages)
2021-10-14 14:32:09 +00:00
{
// get component id
2024-03-02 20:10:30 +00:00
$id = (int) CFactory::_('Config')->component_id;
2021-10-14 14:32:09 +00:00
// check if there is active
2023-10-18 07:28:04 +00:00
if (ArrayHelper::check($this->activeComponents) && isset($this->activeComponents[$id]))
2021-10-14 14:32:09 +00:00
{
// set file name
$file_name = 'com_' . $this->activeComponentsNames[$id];
// extrude the languages that should not remain in the module
2024-03-02 20:10:30 +00:00
$this->extrudeLanguages($id, $languages, CFactory::_('Config')->lang_tag, $file_name);
2021-10-14 14:32:09 +00:00
}
// build the language packages
2024-03-02 20:10:30 +00:00
$this->buildLanguages($id, CFactory::_('Config')->lang_tag);
2021-10-14 14:32:09 +00:00
}
/**
* Extruder of the languages
*
* @return void
*
* @since 1.0
*/
2024-03-02 20:10:30 +00:00
protected function extrudeLanguages(&$id, &$languages, $langTag, &$file_name, $target_client = 'both')
2021-10-14 14:32:09 +00:00
{
2024-03-02 20:10:30 +00:00
$mainLangLoader = [];
2021-10-14 14:32:09 +00:00
// check if this id was set before
if (!isset($this->languages[$id]))
{
2024-03-02 20:10:30 +00:00
$this->languages[$id] = [];
$this->buildDetails[$id] = [];
2021-10-14 14:32:09 +00:00
}
// check if this file name was set before
if (!isset($this->languages[$id][$file_name]))
{
2024-03-02 20:10:30 +00:00
$this->languages[$id][$file_name] = [];
2021-10-14 14:32:09 +00:00
}
// set all the extra languages not excluded
foreach ($languages as $key => $language)
{
2023-10-18 07:28:04 +00:00
if ($key !== $langTag && ArrayHelper::check($language) && (!isset($this->excludedLang[$id]) || !in_array($key, $this->excludedLang[$id])))
2021-10-14 14:32:09 +00:00
{
// add to our bucket
$this->languages[$id][$file_name][$key] = $language;
// remove from the JCB build
unset($languages[$key]);
}
// count the area strings
if ($langTag === $key)
{
foreach ($language as $area => $languageStrings)
{
$mainLangLoader[$area] = count($languageStrings);
}
}
}
// store details for build
2024-03-02 20:10:30 +00:00
$this->buildDetails[$id][$file_name] = [$langTag => $mainLangLoader, 'target_client' => $target_client];
2021-10-14 14:32:09 +00:00
}
/**
* Start the building of the languages packages
*
* @return void
*
*/
2024-03-02 20:10:30 +00:00
protected function buildLanguages(&$id, $langTag)
2021-10-14 14:32:09 +00:00
{
2023-10-18 07:28:04 +00:00
if (isset($this->languages[$id]) && ArrayHelper::check($this->languages[$id]))
2021-10-14 14:32:09 +00:00
{
// rest xml array
2024-03-02 20:10:30 +00:00
$langXML = [];
$langNames = [];
$langPackages = [];
$langZIPNames = [];
$langXMLNames = [];
2021-10-14 14:32:09 +00:00
$versionName = $this->activeComponentsNames[$id] . '_v' . str_replace('.', '_', $this->component_version . '__J' . $this->joomlaVersion);
foreach ($this->languages[$id] as $file_name => $languages)
{
2023-10-18 07:28:04 +00:00
if (ArrayHelper::check($languages) && isset($this->buildDetails[$id][$file_name][$langTag]))
2021-10-14 14:32:09 +00:00
{
// get the main lang loader
$mainLangLoader = $this->buildDetails[$id][$file_name][$langTag];
// get the target client
$target_client = $this->buildDetails[$id][$file_name]['target_client'];
foreach ($languages as $tag => $areas)
{
// trim the tag
$tag = trim($tag);
// get language name
$langName = $this->getLanguageName($tag);
2023-10-18 07:28:04 +00:00
$langCodeName = StringHelper::safe($langName, 'F');
2021-10-14 14:32:09 +00:00
// set the file folder name
$langFolderFileName = $langCodeName . '_' . $versionName;
// set the main folder path
$main_path = $this->compilerPath . '/' . $langFolderFileName . '/';
// set the language name for later
$langNames[$main_path] = $langName;
// set the lang zip name for later
$langZIPNames[$main_path] = $langFolderFileName;
// set the lang xml name for later
$langXMLNames[$main_path] = $langCodeName . '_' . $this->activeComponentsNames[$id] ;
// we must check if old folder is found and remove it
if (!isset($this->languageTracker[$main_path]) && JFolder::exists($main_path))
{
// remove the main folder
ComponentbuilderHelper::removeFolder($main_path);
// do not remove it again
$this->languageTracker[$main_path] = true;
}
// check if exist and create if not
if (!JFolder::exists($main_path))
{
JFolder::create($main_path);
// count the folder created
2023-02-05 21:57:58 +00:00
CFactory::_('Utilities.Counter')->folder++;
2021-10-14 14:32:09 +00:00
}
foreach ($areas as $area => $languageStrings)
{
// get the file name
$fileName = $this->getLanguageFileName($file_name, $tag, $area);
// check if language should be added
if ($this->shouldLanguageBeAdded($tag, $languageStrings, $mainLangLoader[$area], $fileName) && ($actions = $this->getLangActions($file_name, $tag, $area, $target_client)) !== false)
{
// set the language data
$lang = array_map(
function ($langstring, $placeholder) {
return $placeholder . '="' . $langstring . '"';
}, array_values($languageStrings),
array_keys($languageStrings)
);
// set the line counter
2023-02-05 21:57:58 +00:00
CFactory::_('Utilities.Counter')->line += count(
2021-10-14 14:32:09 +00:00
(array) $lang
);
// check that the main folder exist
foreach ($actions as $act)
{
$client_path = $main_path . $act['target_client'] . '/';
// check if exist and create if not
if (!JFolder::exists($client_path))
{
JFolder::create($client_path);
// count the folder created
$this->folderCount++;
}
// write the language data to a file
ComponentbuilderHelper::writeFile(
$client_path . $act['file_name'], implode(PHP_EOL, $lang)
);
// count the file created
2023-02-05 21:57:58 +00:00
CFactory::_('Utilities.Counter')->line++;
2021-10-14 14:32:09 +00:00
// build xml strings
if (!isset($langXML[$main_path]))
{
$langXML[$main_path] = array();
$langPackages[$main_path] = array();
}
if (!isset($langXML[$main_path][$act['target_client']]))
{
$langXML[$main_path][$act['target_client']] = array();
}
// set the package targets
$langPackages[$main_path][$act['target_client']] = $act['target'];
$langXML[$main_path][$act['target_client']][] = $act['file_name'];
}
// clear memory
unset($lang);
}
}
}
}
}
// load the lang xml
2023-10-18 07:28:04 +00:00
if (ArrayHelper::check($langXML))
2021-10-14 14:32:09 +00:00
{
foreach ($langXML as $main_path => $target_clients)
{
// get the XML
$xml = str_replace(
2023-10-18 07:28:04 +00:00
array_keys(CFactory::_('Compiler.Builder.Content.One')->allActive()),
array_values(CFactory::_('Compiler.Builder.Content.One')->allActive()),
2021-10-14 14:32:09 +00:00
$this->getLanguageXML($target_clients, $langPackages[$main_path], $langNames[$main_path])
);
// get the XML File Name
$xmlFileName = $langXMLNames[$main_path] . '.xml';
// write the language data to a file
ComponentbuilderHelper::writeFile(
$main_path . $xmlFileName, $xml
);
// set the zip full path
$zipPath = $this->tempPath . '/' . $langZIPNames[$main_path] . '.zip';
// now zip the package
if (ComponentbuilderHelper::zip(
$main_path, $zipPath
))
{
// now remove the package
ComponentbuilderHelper::removeFolder($main_path);
}
}
}
}
}
/**
* get the language xml
*
* @return string
*
*/
protected function getLanguageXML(&$target_clients, &$targets, &$language)
{
$xml = '<?xml version="1.0" encoding="utf-8"?>';
$xml .= PHP_EOL . '<extension type="file" version="3.2" method="upgrade">';
2023-02-05 21:57:58 +00:00
$xml .= PHP_EOL . Indent::_(1) . '<name>' . Placefix::_h('Component') . ' - ' . $language . ' Language Pack</name>';
$xml .= PHP_EOL . Indent::_(1) . '<creationDate>' . Placefix::_h('BUILDDATE') . '</creationDate>';
$xml .= PHP_EOL . Indent::_(1) . '<author>' . Placefix::_h('AUTHOR') . '</author>';
$xml .= PHP_EOL . Indent::_(1) . '<authorEmail>' . Placefix::_h('AUTHOREMAIL') . '</authorEmail>';
$xml .= PHP_EOL . Indent::_(1) . '<authorUrl>' . Placefix::_h('AUTHORWEBSITE') . '</authorUrl>';
$xml .= PHP_EOL . Indent::_(1) . '<copyright>' . Placefix::_h('COPYRIGHT') . '</copyright>';
$xml .= PHP_EOL . Indent::_(1) . '<license>' . Placefix::_h('LICENSE') . '</license>';
$xml .= PHP_EOL . Indent::_(1) . '<version>' . Placefix::_h('ACTUALVERSION') . '</version>';
$xml .= PHP_EOL . Indent::_(1) . '<description>' . $language . ' Language Pack - ' . Placefix::_h('SHORT_DESCRIPTION') . '</description>';
$xml .= PHP_EOL . Indent::_(1) . '<fileset>';
2021-10-14 14:32:09 +00:00
foreach ($target_clients as $target_client => $files)
{
2023-02-05 21:57:58 +00:00
$xml .= PHP_EOL . Indent::_(2) . '<files folder="' . $target_client . '" target="' . $targets[$target_client] . '">';
2021-10-14 14:32:09 +00:00
foreach ($files as $file)
{
2023-02-05 21:57:58 +00:00
$xml .= PHP_EOL . Indent::_(3) . '<filename>' . $file . '</filename>';
2021-10-14 14:32:09 +00:00
}
2023-02-05 21:57:58 +00:00
$xml .= PHP_EOL . Indent::_(2) . '</files>';
2021-10-14 14:32:09 +00:00
}
2023-02-05 21:57:58 +00:00
$xml .= PHP_EOL . Indent::_(1) . '</fileset>';
2021-10-14 14:32:09 +00:00
$xml .= PHP_EOL . '</extension>';
return $xml;
}
/**
* get the language name
*
* @return string
*
*/
protected function getLanguageName(&$tag)
{
if (!isset($this->languageNames[$tag]))
{
2024-03-02 20:10:30 +00:00
if (($name = GetHelper::var('language', $tag, 'langtag', 'name')) !== false)
2021-10-14 14:32:09 +00:00
{
$this->languageNames[$tag] = $name;
}
else
{
$this->languageNames[$tag] = $tag;
}
}
return $this->languageNames[$tag];
}
/**
* get the language actions
*
* @return array
*
*/
protected function getLangActions(&$file_name, &$tag, &$area, &$target_client)
{
// component extention type
if (strpos($file_name, 'com_') !== false)
{
$target_client = 'admin';
$target = 'administrator/language/';
if (strpos($area, 'site') !== false)
{
$target_client = 'site';
$target = 'language/';
}
return array(
array(
'target_client' => $target_client,
'target' => $target . $tag,
'file_name' => $this->getLanguageFileName($file_name, $tag, $area)
)
);
}
elseif ('admin' === $target_client)
{
$target = 'administrator/language/';
}
else
{
$target = 'language/';
}
// module/plugin extension type (TODO we return both for now)
return array(
array(
'target_client' => $target_client,
'target' => $target . $tag,
'file_name' => $this->getLanguageFileName($file_name, $tag, $area)
),
array(
'target_client' => $target_client,
'target' => $target . $tag,
'file_name' => $this->getLanguageFileName($file_name, $tag, $area, '.sys')
)
);
}
/**
* get the language file name
*
* @return string
*
*/
protected function getLanguageFileName(&$file_name, &$tag, &$area, $type = '')
{
// component extension type
if (strpos($file_name, 'com_') !== false)
{
if (strpos($area, 'sys') !== false)
{
$type = '.sys';
}
}
// file name
return $tag . '.' . $file_name . $type . '.ini';
}
/**
* check if a translation should be added
*
* @return bool
*
*/
protected function shouldLanguageBeAdded(&$tag, &$languageStrings, &$total,
&$file_name
) {
// only log messages for none $this->langTag translations
if ($this->langTag !== $tag)
{
$langStringNr = count($languageStrings);
$langStringSum = ComponentbuilderHelper::bcmath(
'mul', $langStringNr, 100
);
$percentage = ComponentbuilderHelper::bcmath(
'div', $langStringSum, $total
);
$stringNAme = ($langStringNr == 1) ? '(string '
. $tag . ' translated)'
: '(strings ' . $tag . ' translated)';
2024-03-02 20:10:30 +00:00
2021-10-14 14:32:09 +00:00
// force load if debug lines are added
if (!$this->debugLinenr)
{
// check if we should install this translation
if ($percentage < $this->percentageLanguageAdd)
{
// dont add
2024-03-02 20:10:30 +00:00
CFactory::_('Compiler.Builder.Language.Messages')->set("exclude.$file_name",
'<b>' . $total . '</b>(total '
2021-10-14 14:32:09 +00:00
. $this->langTag . ' strings) only <b>'
. $langStringNr . '</b>' . $stringNAme
2024-03-02 20:10:30 +00:00
. ' = ' . $percentage
);
2021-10-14 14:32:09 +00:00
return false;
}
}
2024-03-02 20:10:30 +00:00
2021-10-14 14:32:09 +00:00
// show if it was added as well
2024-03-02 20:10:30 +00:00
CFactory::_('Compiler.Builder.Language.Messages')->set("include.$file_name",
'<b>' . $total . '</b>(total '
2021-10-14 14:32:09 +00:00
. $this->langTag . ' strings) and <b>'
. $langStringNr . '</b>' . $stringNAme . ' = '
2024-03-02 20:10:30 +00:00
. $percentage
);
2021-10-14 14:32:09 +00:00
}
return true;
}
2023-02-05 21:57:58 +00:00
2021-10-14 14:32:09 +00:00
}