42]. * @param Locale|string|null $languageKey The language key or null for using the current language from the system * @return string|null The value from LOCAL_LANG or null if no translation was found. */ public static function translate(string $key, ?string $extensionName = null, ?array $arguments = null, Locale|string|null $languageKey = null, ?ServerRequestInterface $request = null): ?string { if ($key === '') { // Early return guard: returns null if the key was empty, because the key may be a dynamic value // (from for example Fluid). Returning null allows null coalescing to a default value when that happens. return null; } $languageFilePath = null; if (str_starts_with($key, 'LLL:EXT:')) { $keyParts = explode(':', $key); unset($keyParts[0]); $key = array_pop($keyParts); $languageFilePath = implode(':', $keyParts); } elseif ($extensionName) { if (str_contains($extensionName, '.')) { // We assume this is a valid domain now $languageFilePath = $extensionName; $extensionName = self::getExtensionNameFromDomain($languageFilePath); } else { $languageFilePath = GeneralUtility::camelCaseToLowerCaseUnderscored($extensionName) . '.messages'; } } elseif (str_contains($key, ':')) { [$languageFilePath, $key, $extensionName] = self::extractLanguageInfoFromDomainString($key); } if ($languageFilePath === null || $key === '') { throw new \InvalidArgumentException( 'Parameter $extensionName cannot be empty if a fully-qualified key is not specified.', 1498144052 ); } $request = $request ?? $GLOBALS['TYPO3_REQUEST'] ?? null; $locale = self::getLocale($languageKey, $request); $languageService = GeneralUtility::makeInstance(LanguageServiceFactory::class)->create($locale); if (!empty($extensionName) && $request instanceof ServerRequestInterface) { $typoScript = $request->getAttribute('frontend.typoscript'); if ($typoScript instanceof FrontendTypoScript) { // Loads local-language values by looking for a "locallang.xlf" file in the plugin resources directory and if found includes it. // Locallang values set in the TypoScript property "_LOCAL_LANG" are merged onto the values found in the "locallang.xlf" file. $overrideLabels = $languageService->loadTypoScriptLabelsFromExtension($extensionName, $typoScript, self::getPluginName($request)); if ($overrideLabels !== []) { $languageService->overrideLabels($languageFilePath, $overrideLabels); } } } try { return $languageService->translate($key, $languageFilePath, $arguments ?? []); } catch (\ValueError $e) { return sprintf('Error: could not translate key "%s" with value "%s" and %d argument(s)!', $key, $e->getMessage(), count($arguments)); } } /** * Resolves the currently active locale. * Using the Locales factory, as it handles dependencies (e.g. "de-AT" falls back to "de"). */ protected static function getLocale(Locale|string|null $localeOrLanguageKey, ?ServerRequestInterface $request): Locale { if ($localeOrLanguageKey instanceof Locale) { return $localeOrLanguageKey; } $localeFactory = GeneralUtility::makeInstance(Locales::class); if (is_string($localeOrLanguageKey)) { return $localeFactory->createLocale($localeOrLanguageKey); } return $localeFactory->createLocaleFromRequest($request); } /** * Allow plugin.tx_myextension._LOCAL_LANG and plugin.tx_myextension_myplugin._LOCAL_LANG */ protected static function getPluginName(?ServerRequestInterface $request): string { if ($request instanceof Request) { return strtolower($request->getPluginName()); } return ''; } private static function getExtensionNameFromDomain(string $possibleDomain): ?string { [$extensionName, $filePart] = explode('.', $possibleDomain, 2); if ($filePart !== 'messages') { return null; } return $extensionName; } /** * @return array */ private static function extractLanguageInfoFromDomainString(string $key): array { $languageFilePath = null; $extensionName = null; if (str_starts_with($key, 'LLL:')) { $key = substr($key, 4); } [$possibleDomain, $possibleId] = explode(':', $key, 2); $domainMapper = GeneralUtility::makeInstance(TranslationDomainMapper::class); $domainResolver = GeneralUtility::makeInstance(TranslationDomainResolver::class); if ($domainResolver->isValidDomainName($possibleDomain) && $domainMapper->mapDomainToFileName($possibleDomain) !== $possibleDomain) { $languageFilePath = $possibleDomain; $key = trim($possibleId); $extensionName = self::getExtensionNameFromDomain($languageFilePath); } return [ $languageFilePath, $key, $extensionName, ]; } }