PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 4.9.2
WP STAGING – WordPress Backup, Restore, Migration & Clone v4.9.2
4.9.2 4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Language / Language.php
wp-staging / Framework / Language Last commit date
Language.php 1 day ago
Language.php
322 lines
1 <?php
2
3 namespace WPStaging\Framework\Language;
4
5 use WPStaging\Framework\Facades\Hooks;
6
7 class Language
8 {
9 /** @var string */
10 const HOOK_LOAD_MO_FILES = 'wpstg.language.load_mo_files';
11
12 /** @var string */
13 const TEXT_DOMAIN = 'wp-staging';
14
15 const FILTER_PLUGIN_LOCALE = 'plugin_locale';
16
17 /**
18 * @return void
19 */
20 public function load()
21 {
22 /** @noinspection NullPointerExceptionInspection */
23 $pluginLangDirectory = WPSTG_PLUGIN_DIR . 'languages/';
24 $wpLangDirectory = $this->getLangDirectory();
25
26 if (function_exists('get_user_locale')) {
27 $locale = get_user_locale();
28 } else {
29 $locale = get_locale();
30 }
31
32 // Traditional WP plugin locale filter
33 $locale = apply_filters(self::FILTER_PLUGIN_LOCALE, $locale, self::TEXT_DOMAIN);
34 $localMoFile = $this->getLocalMoFile($locale);
35 $globalMoFile = $this->getGlobalMoFile($locale);
36 // Unfiltered mo file name
37 $actualMoFile = sprintf('%1$s-%2$s.mo', self::TEXT_DOMAIN, $locale);
38
39 // Setup paths to current locale file
40 $moFileLocal = $pluginLangDirectory . $localMoFile;
41 $moFilesGlobal = [];
42 if ($globalMoFile !== $actualMoFile) {
43 $moFilesGlobal[] = sprintf('%s/%s/%s', $wpLangDirectory, 'plugins', $actualMoFile);
44 }
45
46 $moFilesGlobal[] = sprintf('%s/%s/%s', $wpLangDirectory, 'plugins', $globalMoFile);
47
48 // Internal Use Only. Use for loading languages files
49 Hooks::callInternalHook(self::HOOK_LOAD_MO_FILES, [$locale, $moFileLocal, $moFilesGlobal]);
50 }
51
52 /**
53 * Get the language code of the current locale, e.g. de, en, it, etc.
54 * @return string
55 */
56 public function getLocaleLanguageCode(): string
57 {
58 if (function_exists('get_user_locale')) {
59 $locale = get_user_locale();
60 } else {
61 $locale = get_locale();
62 }
63 return substr($locale, 0, 2);
64 }
65
66 /**
67 * Map of locale prefixes/codes to the short code used in our .mo file names.
68 * Order matters: longer prefixes must come before shorter ones so that
69 * e.g. 'zh_CN' matches before a hypothetical 'zh_' entry, and 'pt_BR'
70 * matches before a hypothetical 'pt_' entry.
71 */
72 const LOCALE_TO_FILE_CODE = [
73 'de_' => 'de',
74 'es_' => 'es',
75 'fr_' => 'fr',
76 'it_' => 'it',
77 'nl_' => 'nl',
78 'pl_' => 'pl',
79 'ru_' => 'ru',
80 'tr_' => 'tr',
81 'pt_BR' => 'pt_BR',
82 'zh_CN' => 'zh_CN',
83 'ja' => 'ja',
84 ];
85
86 /**
87 * Map of short file codes to their full WordPress locale form used by global .mo files.
88 */
89 const FILE_CODE_TO_GLOBAL_LOCALE = [
90 'de' => 'de_DE',
91 'es' => 'es_ES',
92 'fr' => 'fr_FR',
93 'it' => 'it_IT',
94 'nl' => 'nl_NL',
95 'pl' => 'pl_PL',
96 'ru' => 'ru_RU',
97 'tr' => 'tr_TR',
98 'pt_BR' => 'pt_BR',
99 'zh_CN' => 'zh_CN',
100 'ja' => 'ja',
101 ];
102
103 /**
104 * Resolve a WordPress locale to the language code used in our bundled .mo files.
105 *
106 * @param string $locale
107 * @return string|null The resolved code, or null when no bundled translation exists.
108 */
109 private function resolveFileCode(string $locale)
110 {
111 foreach (self::LOCALE_TO_FILE_CODE as $prefix => $code) {
112 if (strpos($locale, $prefix) === 0 || $locale === $code) {
113 return $code;
114 }
115 }
116
117 return null;
118 }
119
120 protected function getLocalMoFile(string $locale): string
121 {
122 $code = $this->resolveFileCode($locale);
123 if ($code !== null) {
124 $locale = $code;
125 }
126
127 return sprintf('%1$s-%2$s.mo', self::TEXT_DOMAIN, $locale);
128 }
129
130 protected function getGlobalMoFile(string $locale): string
131 {
132 $code = $this->resolveFileCode($locale);
133 if ($code !== null && isset(self::FILE_CODE_TO_GLOBAL_LOCALE[$code])) {
134 $locale = self::FILE_CODE_TO_GLOBAL_LOCALE[$code];
135 }
136
137 return sprintf('%1$s-%2$s.mo', self::TEXT_DOMAIN, $locale);
138 }
139
140 /**
141 * Rewrite a checkout URL for the current locale.
142 * German locales (de_DE, de_AT, de_CH, de_DE_formal, …) use /de/kaufen/ instead of /checkout/.
143 */
144 public static function localizeCheckoutUrl(string $url): string
145 {
146 $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
147 if (strpos($locale, 'de_') === 0) {
148 return str_replace('/checkout/', '/de/kaufen/', $url);
149 }
150
151 return $url;
152 }
153
154 /**
155 * Rewrite a pricing URL for the current locale.
156 * German locales use /de/#pricing instead of /#pricing.
157 */
158 public static function localizePricingUrl(string $url): string
159 {
160 $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
161 if (strpos($locale, 'de_') === 0) {
162 return str_replace('wp-staging.com/#', 'wp-staging.com/de/#', $url);
163 }
164
165 return $url;
166 }
167
168 /**
169 * Build the localized wp-staging.com pricing-table URL for an in-plugin
170 * "upgrade to Pro" CTA.
171 *
172 * The language path is chosen from the current admin user's locale (falling
173 * back to the site locale), so users land on the pricing table in their own
174 * language. Locales the marketing site does not ship fall back to the
175 * English pricing table. When a $context is given it is added as utm_content
176 * — before the #pricing anchor — so the click is attributable in Matomo.
177 *
178 * @param string $context Optional utm_content slug identifying the link.
179 * Sanitized to [a-z0-9_]; anything else is stripped.
180 * @param string $source utm_source for the click. Defaults to
181 * "wp-staging-free"; pass a Pro/licensing source for
182 * CTAs shown to licensed users. Sanitized to
183 * [a-z0-9_-]; empty input falls back to the default.
184 */
185 public static function getUpgradeUrl(string $context = '', string $source = 'wp-staging-free'): string
186 {
187 // wp-staging.com only ships these languages; every other locale (en,
188 // nl, ru, tr, zh, …) falls back to the English pricing table at "/".
189 $localePaths = [
190 'de' => '/de/',
191 'it' => '/it/',
192 'es' => '/es/',
193 'fr' => '/fr/',
194 'pt' => '/pt/',
195 'pl' => '/pl/',
196 'ja' => '/ja/',
197 ];
198
199 $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
200 $prefix = strtolower(substr($locale, 0, 2));
201 $path = isset($localePaths[$prefix]) ? $localePaths[$prefix] : '/';
202
203 $base = 'https://wp-staging.com' . $path;
204
205 $context = preg_replace('/[^a-z0-9_]/', '', strtolower($context));
206 if ($context === '') {
207 return $base . '#pricing';
208 }
209
210 $source = preg_replace('/[^a-z0-9_-]/', '', strtolower($source));
211 if ($source === '') {
212 $source = 'wp-staging-free';
213 }
214
215 // Query string must precede the #pricing anchor for the link to both
216 // track and scroll to the pricing table.
217 $query = http_build_query([
218 'utm_source' => $source,
219 'utm_medium' => 'plugin',
220 'utm_campaign' => 'pro_upgrade',
221 'utm_content' => $context,
222 ]);
223
224 return $base . '?' . $query . '#pricing';
225 }
226
227 /**
228 * Rewrite the support URL for the current locale.
229 * German locales use /de/support/ instead of /support/.
230 */
231 public static function localizeSupportUrl(string $url): string
232 {
233 $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
234 if (strpos($locale, 'de_') === 0) {
235 return str_replace('/support/', '/de/support/', $url);
236 }
237
238 return $url;
239 }
240
241 /**
242 * Rewrite a wp-staging.com homepage URL for the current locale.
243 * German locales insert /de/ after the domain.
244 */
245 public static function localizeHomepageUrl(string $url): string
246 {
247 $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
248 if (strpos($locale, 'de_') === 0) {
249 return str_replace('wp-staging.com/', 'wp-staging.com/de/', $url);
250 }
251
252 return $url;
253 }
254
255 /**
256 * Rewrite any wp-staging.com URL for the current locale.
257 * Inserts /de/ after the domain for German locales.
258 * Works with bare URLs, URLs with paths, and fragment-only URLs like /#pricing.
259 */
260 public static function localizeUrl(string $url): string
261 {
262 $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
263 if (strpos($locale, 'de_') !== 0) {
264 return $url;
265 }
266
267 if (strpos($url, 'wp-staging.com/de/') !== false) {
268 return $url;
269 }
270
271 return preg_replace(
272 '#(https?://wp-staging\.com)/?#',
273 '$1/de/',
274 $url,
275 1
276 );
277 }
278
279 /**
280 * Rewrite a wp-staging.com docs URL for the current locale.
281 * Handles articles where the German slug differs from the English one.
282 */
283 public static function localizeDocsUrl(string $url): string
284 {
285 $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
286 if (strpos($locale, 'de_') !== 0) {
287 return $url;
288 }
289
290 $germanDocsMap = [
291 'https://wp-staging.com/docs/how-to-migrate-your-wordpress-site-to-a-new-host/' => 'https://wp-staging.com/de/docs/wordpress-seite-zu-anderem-host-migrieren/',
292 'https://wp-staging.com/docs/documentation/' => 'https://wp-staging.com/de/docs/dokumentation/',
293 'https://wp-staging.com/docs/set-up-wp-staging-cli/' => 'https://wp-staging.com/de/docs/lokale-kopie-deiner-wordpress-seite-erstellen/',
294 'https://wp-staging.com/docs/pull-a-wordpress-site-from-one-server-to-another/' => 'https://wp-staging.com/de/docs/wordpress-seite-von-einem-server-auf-einen-anderen-ziehen/',
295 ];
296
297 // Strip fragment for lookup, re-append after
298 $fragment = '';
299 $hashPos = strpos($url, '#');
300 if ($hashPos !== false) {
301 $fragment = substr($url, $hashPos);
302 $baseUrl = substr($url, 0, $hashPos);
303 } else {
304 $baseUrl = $url;
305 }
306
307 if (isset($germanDocsMap[$baseUrl])) {
308 return $germanDocsMap[$baseUrl] . $fragment;
309 }
310
311 return self::localizeUrl($url);
312 }
313
314 /**
315 * @return string
316 */
317 protected function getLangDirectory(): string
318 {
319 return WP_LANG_DIR;
320 }
321 }
322