Skip to content

Commit 8cacdf8

Browse files
authored
Added localization options to installer for non-English locales (#541)
1 parent 916fd79 commit 8cacdf8

File tree

9 files changed

+467
-7
lines changed

9 files changed

+467
-7
lines changed

app/code/core/Mage/Install/Block/Complete.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@ public function __construct()
1919
parent::__construct();
2020
$this->setTemplate('page/complete.phtml');
2121
}
22+
23+
public function getLanguagePackCommand(): ?string
24+
{
25+
return Mage::getSingleton('install/session')->getLanguagePackCommand() ?: null;
26+
}
2227
}

app/code/core/Mage/Install/Block/Locale.php

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function getLocale(): string
3232

3333
public function getPostUrl(): string
3434
{
35-
return $this->getCurrentStep()->getNextUrl();
35+
return $this->getUrl('*/*/localePost');
3636
}
3737

3838
public function getChangeUrl(): string
@@ -89,4 +89,41 @@ public function getCurrency(): string
8989
{
9090
return Mage::getSingleton('install/session')->getCurrency() ?: Mage::app()->getLocale()->getCurrency();
9191
}
92+
93+
public function needsLocalization(): bool
94+
{
95+
$locale = $this->getLocale();
96+
$parsed = \Locale::parseLocale($locale);
97+
return $locale !== 'en_US' && isset($parsed['region']);
98+
}
99+
100+
public function getCountryName(): string
101+
{
102+
return \Locale::getDisplayRegion($this->getLocale(), 'en');
103+
}
104+
105+
public function getLanguageName(): string
106+
{
107+
return \Locale::getDisplayLanguage($this->getLocale(), 'en');
108+
}
109+
110+
public function hasLanguagePack(): bool
111+
{
112+
return in_array($this->getLocale(), Mage_Install_Helper_Data::AVAILABLE_LANGUAGE_PACKS, true);
113+
}
114+
115+
public function canInstallLanguagePack(): bool
116+
{
117+
return $this->hasLanguagePack() && Mage::helper('install')->isComposerAvailable();
118+
}
119+
120+
public function getLanguagePackName(): string
121+
{
122+
return 'mahocommerce/maho-language-' . strtolower($this->getLocale());
123+
}
124+
125+
public function getLanguagePackCommand(): string
126+
{
127+
return 'composer require ' . $this->getLanguagePackName();
128+
}
92129
}

app/code/core/Mage/Install/Helper/Data.php

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,105 @@
66
* @package Mage_Install
77
* @copyright Copyright (c) 2006-2020 Magento, Inc. (https://magento.com)
88
* @copyright Copyright (c) 2022-2024 The OpenMage Contributors (https://openmage.org)
9+
* @copyright Copyright (c) 2025-2026 Maho (https://mahocommerce.com)
910
* @license https://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
1011
*/
1112

1213
class Mage_Install_Helper_Data extends Mage_Core_Helper_Abstract
1314
{
15+
public const AVAILABLE_LANGUAGE_PACKS = [
16+
'de_DE', 'el_GR', 'es_ES', 'fr_FR', 'it_IT', 'nl_NL', 'pt_BR', 'pt_PT', 'ro_RO',
17+
];
18+
1419
protected $_moduleName = 'Mage_Install';
20+
21+
private static ?string $composerBinary = null;
22+
private static ?string $phpBinary = null;
23+
private static bool $composerChecked = false;
24+
private static bool $phpChecked = false;
25+
26+
/**
27+
* Find the PHP CLI binary, or return null if not available.
28+
*/
29+
public function getPhpBinary(): ?string
30+
{
31+
if (self::$phpChecked) {
32+
return self::$phpBinary;
33+
}
34+
self::$phpChecked = true;
35+
36+
$searchDirs = array_filter(array_unique([
37+
PHP_BINDIR,
38+
dirname(PHP_BINDIR) . '/bin',
39+
'/usr/local/bin',
40+
'/usr/bin',
41+
'/opt/homebrew/bin',
42+
]));
43+
44+
foreach ($searchDirs as $dir) {
45+
$path = $dir . '/php';
46+
if (is_file($path) && is_executable($path)) {
47+
self::$phpBinary = $path;
48+
return self::$phpBinary;
49+
}
50+
}
51+
52+
return null;
53+
}
54+
55+
/**
56+
* Find the composer binary, or return null if not available.
57+
*/
58+
public function getComposerBinary(): ?string
59+
{
60+
if (self::$composerChecked) {
61+
return self::$composerBinary;
62+
}
63+
self::$composerChecked = true;
64+
65+
// Check for composer.phar in project root
66+
$projectPhar = Mage::getBaseDir() . '/composer.phar';
67+
if (is_file($projectPhar)) {
68+
self::$composerBinary = $projectPhar;
69+
return self::$composerBinary;
70+
}
71+
72+
// Search common locations for the composer binary
73+
$searchDirs = array_filter(array_unique([
74+
PHP_BINDIR,
75+
dirname(PHP_BINDIR) . '/bin',
76+
'/usr/local/bin',
77+
'/usr/bin',
78+
'/opt/homebrew/bin',
79+
]));
80+
81+
foreach ($searchDirs as $dir) {
82+
$path = $dir . '/composer';
83+
if (is_file($path) && is_executable($path)) {
84+
self::$composerBinary = $path;
85+
return self::$composerBinary;
86+
}
87+
}
88+
89+
return null;
90+
}
91+
92+
public function isComposerAvailable(): bool
93+
{
94+
$php = $this->getPhpBinary();
95+
$composer = $this->getComposerBinary();
96+
97+
if (!$php || !$composer) {
98+
return false;
99+
}
100+
101+
try {
102+
$process = new \Symfony\Component\Process\Process([$php, $composer, '--version']);
103+
$process->setTimeout(10);
104+
$process->run();
105+
return $process->isSuccessful();
106+
} catch (\Exception $e) {
107+
return false;
108+
}
109+
}
15110
}

app/code/core/Mage/Install/controllers/WizardController.php

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,15 @@ public function localePostAction(): void
126126
$this->_checkIfInstalled();
127127
$step = $this->_getWizard()->getStepByName('locale');
128128

129+
$session = Mage::getSingleton('install/session');
130+
129131
if ($data = $this->getRequest()->getPost('configuration')) {
130-
Mage::getSingleton('install/session')->setLocaleData($data);
132+
$session->setLocaleData($data);
131133
}
132134

135+
$localization = $this->getRequest()->getPost('localization');
136+
$session->setLocalizationData($localization ?: []);
137+
133138
$this->getResponse()->setRedirect($step->getNextUrl());
134139
}
135140

@@ -411,6 +416,8 @@ public function completeAction(): void
411416
return;
412417
}
413418

419+
$this->runLocalizationActions();
420+
414421
$this->_getInstaller()->finish();
415422

416423
$this->_prepareLayout();
@@ -423,6 +430,76 @@ public function completeAction(): void
423430
Mage::getSingleton('install/session')->clear();
424431
}
425432

433+
private function runLocalizationActions(): void
434+
{
435+
$session = Mage::getSingleton('install/session');
436+
$localization = $session->getLocalizationData();
437+
438+
if (empty($localization)) {
439+
return;
440+
}
441+
442+
$locale = (string) $session->getLocale();
443+
if (!$locale || $locale === 'en_US') {
444+
return;
445+
}
446+
447+
$parsed = \Locale::parseLocale($locale);
448+
$countryCode = $parsed['region'] ?? null;
449+
450+
if (!$countryCode) {
451+
return;
452+
}
453+
454+
if (!empty($localization['import_regions'])) {
455+
try {
456+
$importer = new \MahoCLI\Commands\SysDirectoryRegionsImport();
457+
$result = $importer->importRegionsData($countryCode, [
458+
'locales' => $locale,
459+
]);
460+
461+
if (!$result['success']) {
462+
Mage::log('Failed to import regions: ' . ($result['error'] ?? 'Unknown error'), Mage::LOG_WARNING);
463+
}
464+
} catch (Exception $e) {
465+
Mage::logException($e);
466+
}
467+
}
468+
469+
if (!empty($localization['install_langpack'])) {
470+
if (in_array($locale, Mage_Install_Helper_Data::AVAILABLE_LANGUAGE_PACKS, true)) {
471+
$packageName = 'mahocommerce/maho-language-' . strtolower($locale);
472+
$installed = false;
473+
474+
$helper = Mage::helper('install');
475+
$phpBinary = $helper->getPhpBinary();
476+
$composerBinary = $helper->getComposerBinary();
477+
478+
if ($phpBinary && $composerBinary) {
479+
try {
480+
$process = new \Symfony\Component\Process\Process(
481+
[$phpBinary, $composerBinary, 'require', $packageName, '--no-interaction'],
482+
Mage::getBaseDir(),
483+
);
484+
$process->setTimeout(120);
485+
$process->run();
486+
$installed = $process->isSuccessful();
487+
488+
if (!$installed) {
489+
Mage::log('Failed to install language pack: ' . $process->getErrorOutput(), Mage::LOG_WARNING);
490+
}
491+
} catch (Exception $e) {
492+
Mage::logException($e);
493+
}
494+
}
495+
496+
if (!$installed) {
497+
$session->setLanguagePackCommand('composer require ' . $packageName);
498+
}
499+
}
500+
}
501+
}
502+
426503
public function checkHostAction(): void
427504
{
428505
$this->getResponse()->setHeader('Transfer-encoding', '', true);

app/design/install/default/default/template/page/complete.phtml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@
4646

4747
<p><?= $this->__('Get ready to experience Open-Source eCommerce Evolved.') ?></p>
4848

49+
<?php if ($langPackCommand = $this->getLanguagePackCommand()): ?>
50+
<div class="langpack-notice">
51+
<p><?= $this->__('To complete localization, run the following command:') ?></p>
52+
<code><?= $this->escapeHtml($langPackCommand) ?></code>
53+
</div>
54+
<?php endif; ?>
55+
4956
<div class="button-set">
5057
<a target="_blank" href="<?= $this->getUrl('') ?>" class="form-button">
5158
<span><?= $this->__('Go to Frontend') ?></span>

app/design/install/default/default/template/page/locale.phtml

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<?= $this->getMessagesBlock()->toHtml() ?>
2020
</div>
2121

22-
<form action="<?= $this->getPostUrl() ?>" method="get" id="form-validate">
22+
<form action="<?= $this->getPostUrl() ?>" method="post" id="form-validate">
2323
<div class="form-list">
2424
<ul>
2525
<li>
@@ -36,6 +36,59 @@
3636
</li>
3737
</ul>
3838
</div>
39+
40+
<?php if ($this->needsLocalization()): ?>
41+
<div class="localization-actions">
42+
<h4 class="localization-heading"><?= $this->__('Localization Options') ?></h4>
43+
44+
<div class="localization-action">
45+
<div class="localization-action-icon">
46+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
47+
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path>
48+
<circle cx="12" cy="10" r="3"></circle>
49+
</svg>
50+
</div>
51+
<div class="localization-action-content">
52+
<strong><?= $this->__('Import regions/states for %s', $this->escapeHtml($this->getCountryName())) ?></strong>
53+
<span><?= $this->__('Adds states and provinces to address forms') ?></span>
54+
</div>
55+
<div class="localization-action-status">
56+
<label class="localization-toggle">
57+
<input type="checkbox" name="localization[import_regions]" value="1" checked>
58+
<span class="localization-toggle-slider"></span>
59+
</label>
60+
</div>
61+
</div>
62+
63+
<?php if ($this->canInstallLanguagePack()): ?>
64+
<div class="localization-action">
65+
<div class="localization-action-icon">
66+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
67+
<circle cx="12" cy="12" r="10"></circle>
68+
<line x1="2" y1="12" x2="22" y2="12"></line>
69+
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
70+
</svg>
71+
</div>
72+
<div class="localization-action-content">
73+
<strong><?= $this->__('Install %s language pack', $this->escapeHtml($this->getLanguageName())) ?></strong>
74+
<span><?= $this->escapeHtml($this->getLanguagePackName()) ?></span>
75+
</div>
76+
<div class="localization-action-status">
77+
<label class="localization-toggle">
78+
<input type="checkbox" name="localization[install_langpack]" value="1" checked>
79+
<span class="localization-toggle-slider"></span>
80+
</label>
81+
</div>
82+
</div>
83+
<?php elseif ($this->hasLanguagePack()): ?>
84+
<div class="langpack-notice">
85+
<p><?= $this->__('To complete localization, run the following command:') ?></p>
86+
<code><?= $this->escapeHtml($this->getLanguagePackCommand()) ?></code>
87+
</div>
88+
<?php endif; ?>
89+
</div>
90+
<?php endif; ?>
91+
3992
<div class="button-set">
4093
<p class="required">* <?= $this->__('Required Fields') ?></p>
4194
<button class="form-button" type="submit"><span><?= $this->__('Continue') ?></span></button>

app/locale/en_US/Mage_Install.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"(Optional. Leave blank for no prefix)","(Optional. Leave blank for no prefix)"
22
"About, contact & more","About, contact & more"
33
"Additional path added after Base URL to access your Administrative Panel (e.g. admin, backend, control etc.).","Additional path added after Base URL to access your Administrative Panel (e.g. admin, backend, control etc.)."
4+
"Adds states and provinces to address forms","Adds states and provinces to address forms"
45
"Admin","Admin"
56
"Admin Path","Admin Path"
67
"Base URL","Base URL"
@@ -51,6 +52,8 @@
5152
"Importing configuration...","Importing configuration..."
5253
"Importing database...","Importing database..."
5354
"Importing database... (%s/%s)","Importing database... (%s/%s)"
55+
"Import regions/states for %s","Import regions/states for %s"
56+
"Install %s language pack","Install %s language pack"
5457
"Installation Failed","Installation Failed"
5558
"Installation failed","Installation failed"
5659
"Installation Progress","Installation Progress"
@@ -64,6 +67,7 @@
6467
"License","License"
6568
"Locale","Locale"
6669
"Localization","Localization"
70+
"Localization Options","Localization Options"
6771
"Login Information","Login Information"
6872
"Maho Installation Wizard","Maho Installation Wizard"
6973
"Media","Media"
@@ -104,6 +108,7 @@
104108
"The URL ""%s"" is invalid.","The URL ""%s"" is invalid."
105109
"The URL ""%s"" is not accessible.","The URL ""%s"" is not accessible."
106110
"Time Zone","Time Zone"
111+
"To complete localization, run the following command:","To complete localization, run the following command:"
107112
"URL must end with a trailing slash (/)","URL must end with a trailing slash (/)"
108113
"User Name","User Name"
109114
"Username","Username"

0 commit comments

Comments
 (0)