001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.language;
016    
017    import com.liferay.portal.kernel.cache.MultiVMPool;
018    import com.liferay.portal.kernel.cache.PortalCache;
019    import com.liferay.portal.kernel.cache.PortalCacheMapSynchronizeUtil;
020    import com.liferay.portal.kernel.cache.PortalCacheMapSynchronizeUtil.Synchronizer;
021    import com.liferay.portal.kernel.exception.PortalException;
022    import com.liferay.portal.kernel.exception.SystemException;
023    import com.liferay.portal.kernel.language.Language;
024    import com.liferay.portal.kernel.language.LanguageWrapper;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.security.auth.CompanyThreadLocal;
028    import com.liferay.portal.kernel.security.pacl.DoPrivileged;
029    import com.liferay.portal.kernel.util.ArrayUtil;
030    import com.liferay.portal.kernel.util.CharPool;
031    import com.liferay.portal.kernel.util.CookieKeys;
032    import com.liferay.portal.kernel.util.GetterUtil;
033    import com.liferay.portal.kernel.util.HtmlUtil;
034    import com.liferay.portal.kernel.util.JavaConstants;
035    import com.liferay.portal.kernel.util.LocaleUtil;
036    import com.liferay.portal.kernel.util.ObjectValuePair;
037    import com.liferay.portal.kernel.util.ParamUtil;
038    import com.liferay.portal.kernel.util.PropsKeys;
039    import com.liferay.portal.kernel.util.ResourceBundleUtil;
040    import com.liferay.portal.kernel.util.StringBundler;
041    import com.liferay.portal.kernel.util.StringPool;
042    import com.liferay.portal.kernel.util.StringUtil;
043    import com.liferay.portal.kernel.util.Time;
044    import com.liferay.portal.kernel.util.UnicodeProperties;
045    import com.liferay.portal.kernel.util.Validator;
046    import com.liferay.portal.kernel.util.WebKeys;
047    import com.liferay.portal.model.CompanyConstants;
048    import com.liferay.portal.model.Group;
049    import com.liferay.portal.model.GroupConstants;
050    import com.liferay.portal.service.GroupLocalServiceUtil;
051    import com.liferay.portal.theme.ThemeDisplay;
052    import com.liferay.portal.util.PortalUtil;
053    import com.liferay.portal.util.PrefsPropsUtil;
054    import com.liferay.portal.util.PropsValues;
055    import com.liferay.registry.Registry;
056    import com.liferay.registry.RegistryUtil;
057    import com.liferay.registry.dependency.ServiceDependencyListener;
058    import com.liferay.registry.dependency.ServiceDependencyManager;
059    
060    import java.io.Serializable;
061    
062    import java.text.MessageFormat;
063    import java.text.NumberFormat;
064    
065    import java.util.HashMap;
066    import java.util.HashSet;
067    import java.util.List;
068    import java.util.Locale;
069    import java.util.Map;
070    import java.util.ResourceBundle;
071    import java.util.Set;
072    import java.util.concurrent.ConcurrentHashMap;
073    import java.util.regex.Matcher;
074    import java.util.regex.Pattern;
075    
076    import javax.portlet.PortletConfig;
077    import javax.portlet.PortletRequest;
078    
079    import javax.servlet.http.Cookie;
080    import javax.servlet.http.HttpServletRequest;
081    import javax.servlet.http.HttpServletResponse;
082    
083    /**
084     * Provides various translation related functionalities for language keys
085     * specified in portlet configurations and portal resource bundles.
086     *
087     * <p>
088     * You can disable translations by setting the
089     * <code>translations.disabled</code> property to <code>true</code> in
090     * <code>portal.properties</code>.
091     * </p>
092     *
093     * <p>
094     * Depending on the context passed into these methods, the lookup might be
095     * limited to the portal's resource bundle (e.g. when only a locale is passed),
096     * or extended to include an individual portlet's resource bundle (e.g. when a
097     * request object is passed). A portlet's resource bundle overrides the portal's
098     * resources when both are present.
099     * </p>
100     *
101     * @author Brian Wing Shun Chan
102     * @author Andrius Vitkauskas
103     * @author Eduardo Lundgren
104     */
105    @DoPrivileged
106    public class LanguageImpl implements Language, Serializable {
107    
108            public void afterPropertiesSet() {
109                    ServiceDependencyManager serviceDependencyManager =
110                            new ServiceDependencyManager();
111    
112                    serviceDependencyManager.addServiceDependencyListener(
113                            new ServiceDependencyListener() {
114    
115                                    @Override
116                                    public void dependenciesFulfilled() {
117                                            Registry registry = RegistryUtil.getRegistry();
118    
119                                            MultiVMPool multiVMPool = registry.getService(
120                                                    MultiVMPool.class);
121    
122                                            _portalCache =
123                                                    (PortalCache<Long, Serializable>)
124                                                            multiVMPool.getPortalCache(
125                                                                    LanguageImpl.class.getName());
126    
127                                            PortalCacheMapSynchronizeUtil.synchronize(
128                                                    _portalCache, _companyLocalesBags,
129                                                    new Synchronizer<Long, Serializable>() {
130    
131                                                            @Override
132                                                            public void onSynchronize(
133                                                                    Map<? extends Long, ? extends Serializable> map,
134                                                                    Long key, Serializable value, int timeToLive) {
135    
136                                                                    _companyLocalesBags.remove(key);
137                                                            }
138    
139                                                    });
140                                    }
141    
142                                    @Override
143                                    public void destroy() {
144                                    }
145    
146                            }
147                    );
148    
149                    serviceDependencyManager.registerDependencies(MultiVMPool.class);
150            }
151    
152            /**
153             * Returns the translated pattern using the current request's locale or, if
154             * the current request locale is not available, the server's default locale.
155             *
156             * <p>
157             * The lookup is done on the portlet configuration first, and if it's not
158             * found, it is done on the portal's resource bundle. If a translation for a
159             * given key does not exist, this method returns the requested key as the
160             * translation.
161             * </p>
162             *
163             * <p>
164             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
165             * argument, following the standard Java {@link ResourceBundle} notion of
166             * index based substitution.
167             * </p>
168             *
169             * @param  request the request used to determine the current locale
170             * @param  pattern the key to look up in the current locale's resource file.
171             *         The key follows the standard Java resource specification.
172             * @param  argument the single argument to be substituted into the pattern
173             *         and translated, if possible
174             * @return the translated pattern, with the argument substituted in for the
175             *         pattern's placeholder
176             */
177            @Override
178            public String format(
179                    HttpServletRequest request, String pattern, LanguageWrapper argument) {
180    
181                    return format(request, pattern, new LanguageWrapper[] {argument}, true);
182            }
183    
184            /**
185             * Returns the translated pattern using the current request's locale or, if
186             * the current request locale is not available, the server's default locale.
187             *
188             * <p>
189             * The lookup is done on the portlet configuration first, and if it's not
190             * found, it is done on the portal's resource bundle. If a translation for a
191             * given key does not exist, this method returns the requested key as the
192             * translation.
193             * </p>
194             *
195             * <p>
196             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
197             * argument, following the standard Java {@link ResourceBundle} notion of
198             * index based substitution.
199             * </p>
200             *
201             * @param  request the request used to determine the current locale
202             * @param  pattern the key to look up in the current locale's resource file.
203             *         The key follows the standard Java resource specification.
204             * @param  argument the single argument to be substituted into the pattern
205             *         and translated, if possible
206             * @param  translateArguments whether the argument is translated
207             * @return the translated pattern, with the argument substituted in for the
208             *         pattern's placeholder
209             */
210            @Override
211            public String format(
212                    HttpServletRequest request, String pattern, LanguageWrapper argument,
213                    boolean translateArguments) {
214    
215                    return format(
216                            request, pattern, new LanguageWrapper[] {argument},
217                            translateArguments);
218            }
219    
220            /**
221             * Returns the translated pattern using the current request's locale or, if
222             * the current request locale is not available, the server's default locale.
223             *
224             * <p>
225             * The lookup is done on the portlet configuration first, and if it's not
226             * found, it is done on the portal's resource bundle. If a translation for a
227             * given key does not exist, this method returns the requested key as the
228             * translation.
229             * </p>
230             *
231             * <p>
232             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
233             * <code>{2}</code>, etc.) are replaced with the arguments, following the
234             * standard Java {@link ResourceBundle} notion of index based substitution.
235             * </p>
236             *
237             * @param  request the request used to determine the current locale
238             * @param  pattern the key to look up in the current locale's resource file.
239             *         The key follows the standard Java resource specification.
240             * @param  arguments the arguments to be substituted into the pattern and
241             *         translated, if possible
242             * @return the translated pattern, with the arguments substituted in for the
243             *         pattern's placeholders
244             */
245            @Override
246            public String format(
247                    HttpServletRequest request, String pattern,
248                    LanguageWrapper[] arguments) {
249    
250                    return format(request, pattern, arguments, true);
251            }
252    
253            /**
254             * Returns the translated pattern using the current request's locale or, if
255             * the current request locale is not available, the server's default locale.
256             *
257             * <p>
258             * The lookup is done on the portlet configuration first, and if it's not
259             * found, it is done on the portal's resource bundle. If a translation for a
260             * given key does not exist, this method returns the requested key as the
261             * translation.
262             * </p>
263             *
264             * <p>
265             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
266             * <code>{2}</code>, etc.) are replaced with the arguments, following the
267             * standard Java {@link ResourceBundle} notion of index based substitution.
268             * </p>
269             *
270             * @param  request the request used to determine the current locale
271             * @param  pattern the key to look up in the current locale's resource file.
272             *         The key follows the standard Java resource specification.
273             * @param  arguments the arguments to be substituted into the pattern
274             * @param  translateArguments whether the arguments are translated
275             * @return the translated pattern, with the arguments substituted in for the
276             *         pattern's placeholders
277             */
278            @Override
279            public String format(
280                    HttpServletRequest request, String pattern, LanguageWrapper[] arguments,
281                    boolean translateArguments) {
282    
283                    if (PropsValues.TRANSLATIONS_DISABLED) {
284                            return pattern;
285                    }
286    
287                    String value = null;
288    
289                    try {
290                            pattern = get(request, pattern);
291    
292                            if (ArrayUtil.isNotEmpty(arguments)) {
293                                    pattern = _escapePattern(pattern);
294    
295                                    Object[] formattedArguments = new Object[arguments.length];
296    
297                                    for (int i = 0; i < arguments.length; i++) {
298                                            if (translateArguments) {
299                                                    formattedArguments[i] =
300                                                            arguments[i].getBefore() +
301                                                                    get(request, arguments[i].getText()) +
302                                                                            arguments[i].getAfter();
303                                            }
304                                            else {
305                                                    formattedArguments[i] =
306                                                            arguments[i].getBefore() + arguments[i].getText() +
307                                                                    arguments[i].getAfter();
308                                            }
309                                    }
310    
311                                    MessageFormat messageFormat = decorateMessageFormat(
312                                            request, pattern, formattedArguments);
313    
314                                    value = messageFormat.format(formattedArguments);
315                            }
316                            else {
317                                    value = pattern;
318                            }
319                    }
320                    catch (Exception e) {
321                            if (_log.isWarnEnabled()) {
322                                    _log.warn(e, e);
323                            }
324                    }
325    
326                    return value;
327            }
328    
329            /**
330             * Returns the translated pattern using the current request's locale or, if
331             * the current request locale is not available, the server's default locale.
332             *
333             * <p>
334             * The lookup is done on the portlet configuration first, and if it's not
335             * found, it is done on the portal's resource bundle. If a translation for a
336             * given key does not exist, this method returns the requested key as the
337             * translation.
338             * </p>
339             *
340             * <p>
341             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
342             * argument, following the standard Java {@link ResourceBundle} notion of
343             * index based substitution.
344             * </p>
345             *
346             * @param  request the request used to determine the current locale
347             * @param  pattern the key to look up in the current locale's resource file.
348             *         The key follows the standard Java resource specification.
349             * @param  argument the single argument to be substituted into the pattern
350             *         and translated, if possible
351             * @return the translated pattern, with the argument substituted in for the
352             *         pattern's placeholder
353             */
354            @Override
355            public String format(
356                    HttpServletRequest request, String pattern, Object argument) {
357    
358                    return format(request, pattern, new Object[] {argument}, true);
359            }
360    
361            /**
362             * Returns the translated pattern using the current request's locale or, if
363             * the current request locale is not available, the server's default locale.
364             *
365             * <p>
366             * The lookup is done on the portlet configuration first, and if it's not
367             * found, it is done on the portal's resource bundle. If a translation for a
368             * given key does not exist, this method returns the requested key as the
369             * translation.
370             * </p>
371             *
372             * <p>
373             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
374             * argument, following the standard Java {@link ResourceBundle} notion of
375             * index based substitution.
376             * </p>
377             *
378             * @param  request the request used to determine the current locale
379             * @param  pattern the key to look up in the current locale's resource file.
380             *         The key follows the standard Java resource specification.
381             * @param  argument the single argument to be substituted into the pattern
382             *         and translated, if possible
383             * @param  translateArguments whether the argument is translated
384             * @return the translated pattern, with the argument substituted in for the
385             *         pattern's placeholder
386             */
387            @Override
388            public String format(
389                    HttpServletRequest request, String pattern, Object argument,
390                    boolean translateArguments) {
391    
392                    return format(
393                            request, pattern, new Object[] {argument}, translateArguments);
394            }
395    
396            /**
397             * Returns the translated pattern using the current request's locale or, if
398             * the current request locale is not available, the server's default locale.
399             *
400             * <p>
401             * The lookup is done on the portlet configuration first, and if it's not
402             * found, it is done on the portal's resource bundle. If a translation for a
403             * given key does not exist, this method returns the requested key as the
404             * translation.
405             * </p>
406             *
407             * <p>
408             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
409             * <code>{2}</code>, etc.) are replaced with the arguments, following the
410             * standard Java {@link ResourceBundle} notion of index based substitution.
411             * </p>
412             *
413             * @param  request the request used to determine the current locale
414             * @param  pattern the key to look up in the current locale's resource file.
415             *         The key follows the standard Java resource specification.
416             * @param  arguments the arguments to be substituted into the pattern and
417             *         translated, if possible
418             * @return the translated pattern, with the arguments substituted in for the
419             *         pattern's placeholders
420             */
421            @Override
422            public String format(
423                    HttpServletRequest request, String pattern, Object[] arguments) {
424    
425                    return format(request, pattern, arguments, true);
426            }
427    
428            /**
429             * Returns the translated pattern using the current request's locale or, if
430             * the current request locale is not available, the server's default locale.
431             *
432             * <p>
433             * The lookup is done on the portlet configuration first, and if it's not
434             * found, it is done on the portal's resource bundle. If a translation for a
435             * given key does not exist, this method returns the requested key as the
436             * translation.
437             * </p>
438             *
439             * <p>
440             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
441             * <code>{2}</code>, etc.) are replaced with the arguments, following the
442             * standard Java {@link ResourceBundle} notion of index based substitution.
443             * </p>
444             *
445             * @param  request the request used to determine the current locale
446             * @param  pattern the key to look up in the current locale's resource file.
447             *         The key follows the standard Java resource specification.
448             * @param  arguments the arguments to be substituted into the pattern
449             * @param  translateArguments whether the arguments are translated
450             * @return the translated pattern, with the arguments substituted in for the
451             *         pattern's placeholders
452             */
453            @Override
454            public String format(
455                    HttpServletRequest request, String pattern, Object[] arguments,
456                    boolean translateArguments) {
457    
458                    if (PropsValues.TRANSLATIONS_DISABLED) {
459                            return pattern;
460                    }
461    
462                    String value = null;
463    
464                    try {
465                            pattern = get(request, pattern);
466    
467                            if (ArrayUtil.isNotEmpty(arguments)) {
468                                    pattern = _escapePattern(pattern);
469    
470                                    Object[] formattedArguments = new Object[arguments.length];
471    
472                                    for (int i = 0; i < arguments.length; i++) {
473                                            if (translateArguments) {
474                                                    formattedArguments[i] = get(
475                                                            request, arguments[i].toString());
476                                            }
477                                            else {
478                                                    formattedArguments[i] = arguments[i];
479                                            }
480                                    }
481    
482                                    MessageFormat messageFormat = decorateMessageFormat(
483                                            request, pattern, formattedArguments);
484    
485                                    value = messageFormat.format(formattedArguments);
486                            }
487                            else {
488                                    value = pattern;
489                            }
490                    }
491                    catch (Exception e) {
492                            if (_log.isWarnEnabled()) {
493                                    _log.warn(e, e);
494                            }
495                    }
496    
497                    return value;
498            }
499    
500            /**
501             * Returns the translated pattern using the locale or, if the locale is not
502             * available, the server's default locale.
503             *
504             * <p>
505             * The lookup is done on the portal's resource bundle. If a translation for
506             * a given key does not exist, this method returns the requested key as the
507             * translation.
508             * </p>
509             *
510             * <p>
511             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
512             * <code>{2}</code>, etc.) are replaced with the arguments, following the
513             * standard Java {@link ResourceBundle} notion of index based substitution.
514             * </p>
515             *
516             * @param  locale the locale to translate to
517             * @param  pattern the key to look up in the current locale's resource file.
518             *         The key follows the standard Java resource specification.
519             * @param  arguments the arguments to be substituted into the pattern
520             * @return the translated pattern, with the arguments substituted in for the
521             *         pattern's placeholders
522             */
523            @Override
524            public String format(
525                    Locale locale, String pattern, List<Object> arguments) {
526    
527                    return format(locale, pattern, arguments.toArray(), true);
528            }
529    
530            /**
531             * Returns the translated pattern using the locale or, if the locale is not
532             * available, the server's default locale.
533             *
534             * <p>
535             * The lookup is done on the portal's resource bundle. If a translation for
536             * a given key does not exist, this method returns the requested key as the
537             * translation.
538             * </p>
539             *
540             * <p>
541             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
542             * argument, following the standard Java {@link ResourceBundle} notion of
543             * index based substitution.
544             * </p>
545             *
546             * @param  locale the locale to translate to
547             * @param  pattern the key to look up in the current locale's resource file.
548             *         The key follows the standard Java resource specification.
549             * @param  argument the argument to be substituted into the pattern
550             * @return the translated pattern, with the argument substituted in for the
551             *         pattern's placeholder
552             */
553            @Override
554            public String format(Locale locale, String pattern, Object argument) {
555                    return format(locale, pattern, new Object[] {argument}, true);
556            }
557    
558            /**
559             * Returns the translated pattern using the locale or, if the locale is not
560             * available, the server's default locale.
561             *
562             * <p>
563             * The lookup is done on the portal's resource bundle. If a translation for
564             * a given key does not exist, this method returns the requested key as the
565             * translation.
566             * </p>
567             *
568             * <p>
569             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
570             * argument, following the standard Java {@link ResourceBundle} notion of
571             * index based substitution.
572             * </p>
573             *
574             * @param  locale the locale to translate to
575             * @param  pattern the key to look up in the current locale's resource file.
576             *         The key follows the standard Java resource specification.
577             * @param  argument the argument to be substituted into the pattern
578             * @param  translateArguments whether the argument is translated
579             * @return the translated pattern, with the argument substituted in for the
580             *         pattern's placeholder
581             */
582            @Override
583            public String format(
584                    Locale locale, String pattern, Object argument,
585                    boolean translateArguments) {
586    
587                    return format(
588                            locale, pattern, new Object[] {argument}, translateArguments);
589            }
590    
591            /**
592             * Returns the translated pattern using the locale or, if the locale is not
593             * available, the server's default locale.
594             *
595             * <p>
596             * The lookup is done on the portal's resource bundle. If a translation for
597             * a given key does not exist, this method returns the requested key as the
598             * translation.
599             * </p>
600             *
601             * <p>
602             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
603             * <code>{2}</code>, etc.) are replaced with the arguments, following the
604             * standard Java {@link ResourceBundle} notion of index based substitution.
605             * </p>
606             *
607             * @param  locale the locale to translate to
608             * @param  pattern the key to look up in the current locale's resource file.
609             *         The key follows the standard Java resource specification.
610             * @param  arguments the arguments to be substituted into the pattern
611             * @return the translated pattern, with the arguments substituted in for the
612             *         pattern's placeholders
613             */
614            @Override
615            public String format(Locale locale, String pattern, Object[] arguments) {
616                    return format(locale, pattern, arguments, true);
617            }
618    
619            /**
620             * Returns the translated pattern using the locale or, if the locale is not
621             * available, the server's default locale.
622             *
623             * <p>
624             * The lookup is done on the portal's resource bundle. If a translation for
625             * a given key does not exist, this method returns the requested key as the
626             * translation.
627             * </p>
628             *
629             * <p>
630             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
631             * <code>{2}</code>, etc.) are replaced with the arguments, following the
632             * standard Java {@link ResourceBundle} notion of index based substitution.
633             * </p>
634             *
635             * @param  locale the locale to translate to
636             * @param  pattern the key to look up in the current locale's resource file.
637             *         The key follows the standard Java resource specification.
638             * @param  arguments the arguments to be substituted into the pattern
639             * @param  translateArguments whether the arguments are translated
640             * @return the translated pattern, with the arguments substituted in for the
641             *         pattern's placeholders
642             */
643            @Override
644            public String format(
645                    Locale locale, String pattern, Object[] arguments,
646                    boolean translateArguments) {
647    
648                    if (PropsValues.TRANSLATIONS_DISABLED) {
649                            return pattern;
650                    }
651    
652                    String value = null;
653    
654                    try {
655                            pattern = get(locale, pattern);
656    
657                            if (ArrayUtil.isNotEmpty(arguments)) {
658                                    pattern = _escapePattern(pattern);
659    
660                                    Object[] formattedArguments = new Object[arguments.length];
661    
662                                    for (int i = 0; i < arguments.length; i++) {
663                                            if (translateArguments) {
664                                                    formattedArguments[i] = get(
665                                                            locale, arguments[i].toString());
666                                            }
667                                            else {
668                                                    formattedArguments[i] = arguments[i];
669                                            }
670                                    }
671    
672                                    MessageFormat messageFormat = decorateMessageFormat(
673                                            locale, pattern, formattedArguments);
674    
675                                    value = messageFormat.format(formattedArguments);
676                            }
677                            else {
678                                    value = pattern;
679                            }
680                    }
681                    catch (Exception e) {
682                            if (_log.isWarnEnabled()) {
683                                    _log.warn(e, e);
684                            }
685                    }
686    
687                    return value;
688            }
689    
690            /**
691             * Returns the translated pattern in the resource bundle or, if the resource
692             * bundle is not available, the untranslated key. If a translation for a
693             * given key does not exist, this method returns the requested key as the
694             * translation.
695             *
696             * <p>
697             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
698             * argument, following the standard Java {@link ResourceBundle} notion of
699             * index based substitution.
700             * </p>
701             *
702             * @param  resourceBundle the requested key's resource bundle
703             * @param  pattern the key to look up in the resource bundle. The key
704             *         follows the standard Java resource specification.
705             * @param  argument the argument to be substituted into the pattern
706             * @return the translated pattern, with the argument substituted in for the
707             *         pattern's placeholder
708             */
709            @Override
710            public String format(
711                    ResourceBundle resourceBundle, String pattern, Object argument) {
712    
713                    return format(resourceBundle, pattern, new Object[] {argument}, true);
714            }
715    
716            /**
717             * Returns the translated pattern in the resource bundle or, if the resource
718             * bundle is not available, the untranslated key. If a translation for a
719             * given key does not exist, this method returns the requested key as the
720             * translation.
721             *
722             * <p>
723             * The substitute placeholder (e.g. <code>{0}</code>) is replaced with the
724             * argument, following the standard Java {@link ResourceBundle} notion of
725             * index based substitution.
726             * </p>
727             *
728             * @param  resourceBundle the requested key's resource bundle
729             * @param  pattern the key to look up in the resource bundle. The key
730             *         follows the standard Java resource specification.
731             * @param  argument the argument to be substituted into the pattern
732             * @param  translateArguments whether the argument is translated
733             * @return the translated pattern, with the argument substituted in for the
734             *         pattern's placeholder
735             */
736            @Override
737            public String format(
738                    ResourceBundle resourceBundle, String pattern, Object argument,
739                    boolean translateArguments) {
740    
741                    return format(
742                            resourceBundle, pattern, new Object[] {argument},
743                            translateArguments);
744            }
745    
746            /**
747             * Returns the translated pattern in the resource bundle or, if the resource
748             * bundle is not available, the untranslated key. If a translation for a
749             * given key does not exist, this method returns the requested key as the
750             * translation.
751             *
752             * <p>
753             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
754             * <code>{2}</code>, etc.) are replaced with the arguments, following the
755             * standard Java {@link ResourceBundle} notion of index based substitution.
756             * </p>
757             *
758             * @param  resourceBundle the requested key's resource bundle
759             * @param  pattern the key to look up in the resource bundle. The key
760             *         follows the standard Java resource specification.
761             * @param  arguments the arguments to be substituted into the pattern
762             * @return the translated pattern, with the arguments substituted in for the
763             *         pattern's placeholder
764             */
765            @Override
766            public String format(
767                    ResourceBundle resourceBundle, String pattern, Object[] arguments) {
768    
769                    return format(resourceBundle, pattern, arguments, true);
770            }
771    
772            /**
773             * Returns the translated pattern in the resource bundle or, if the resource
774             * bundle is not available, the untranslated key. If a translation for a
775             * given key does not exist, this method returns the requested key as the
776             * translation.
777             *
778             * <p>
779             * The substitute placeholders (e.g. <code>{0}</code>, <code>{1}</code>,
780             * <code>{2}</code>, etc.) are replaced with the arguments, following the
781             * standard Java {@link ResourceBundle} notion of index based substitution.
782             * </p>
783             *
784             * @param  resourceBundle the requested key's resource bundle
785             * @param  pattern the key to look up in the resource bundle. The key
786             *         follows the standard Java resource specification.
787             * @param  arguments the arguments to be substituted into the pattern
788             * @param  translateArguments whether the arguments are translated
789             * @return the translated pattern, with the arguments substituted in for the
790             *         pattern's placeholder
791             */
792            @Override
793            public String format(
794                    ResourceBundle resourceBundle, String pattern, Object[] arguments,
795                    boolean translateArguments) {
796    
797                    if (PropsValues.TRANSLATIONS_DISABLED) {
798                            return pattern;
799                    }
800    
801                    String value = null;
802    
803                    try {
804                            pattern = get(resourceBundle, pattern);
805    
806                            if (ArrayUtil.isNotEmpty(arguments)) {
807                                    pattern = _escapePattern(pattern);
808    
809                                    Object[] formattedArguments = new Object[arguments.length];
810    
811                                    for (int i = 0; i < arguments.length; i++) {
812                                            if (translateArguments) {
813                                                    formattedArguments[i] = get(
814                                                            resourceBundle, arguments[i].toString());
815                                            }
816                                            else {
817                                                    formattedArguments[i] = arguments[i];
818                                            }
819                                    }
820    
821                                    MessageFormat messageFormat = decorateMessageFormat(
822                                            resourceBundle.getLocale(), pattern, formattedArguments);
823    
824                                    value = messageFormat.format(formattedArguments);
825                            }
826                            else {
827                                    value = pattern;
828                            }
829                    }
830                    catch (Exception e) {
831                            if (_log.isWarnEnabled()) {
832                                    _log.warn(e, e);
833                            }
834                    }
835    
836                    return value;
837            }
838    
839            /**
840             * Returns the key's translation from the portlet configuration, or from the
841             * portal's resource bundle if the portlet configuration is unavailable.
842             *
843             * @param  request the request used to determine the key's context and
844             *         locale
845             * @param  resourceBundle the requested key's resource bundle
846             * @param  key the translation key
847             * @return the key's translation, or the key if the translation is
848             *         unavailable
849             */
850            @Override
851            public String get(
852                    HttpServletRequest request, ResourceBundle resourceBundle, String key) {
853    
854                    return get(request, resourceBundle, key, key);
855            }
856    
857            @Override
858            public String get(
859                    HttpServletRequest request, ResourceBundle resourceBundle, String key,
860                    String defaultValue) {
861    
862                    String value = _get(resourceBundle, key);
863    
864                    if (value != null) {
865                            return value;
866                    }
867    
868                    return get(request, key, defaultValue);
869            }
870    
871            @Override
872            public String get(HttpServletRequest request, String key) {
873                    return get(request, key, key);
874            }
875    
876            /**
877             * Returns the key's translation from the portlet configuration, or from the
878             * portal's resource bundle if the portlet configuration is unavailable.
879             *
880             * @param  request the request used to determine the key's context and
881             *         locale
882             * @param  key the translation key
883             * @param  defaultValue the value to return if there is no matching
884             *         translation
885             * @return the key's translation, or the default value if the translation is
886             *         unavailable
887             */
888            @Override
889            public String get(
890                    HttpServletRequest request, String key, String defaultValue) {
891    
892                    if ((request == null) || (key == null)) {
893                            return defaultValue;
894                    }
895    
896                    PortletConfig portletConfig = (PortletConfig)request.getAttribute(
897                            JavaConstants.JAVAX_PORTLET_CONFIG);
898    
899                    Locale locale = _getLocale(request);
900    
901                    if (portletConfig == null) {
902                            return get(locale, key, defaultValue);
903                    }
904    
905                    ResourceBundle resourceBundle = portletConfig.getResourceBundle(locale);
906    
907                    if (resourceBundle.containsKey(key)) {
908                            return _get(resourceBundle, key);
909                    }
910    
911                    return get(locale, key, defaultValue);
912            }
913    
914            /**
915             * Returns the key's translation from the portal's resource bundle.
916             *
917             * @param  locale the key's locale
918             * @param  key the translation key
919             * @return the key's translation
920             */
921            @Override
922            public String get(Locale locale, String key) {
923                    return get(locale, key, key);
924            }
925    
926            /**
927             * Returns the key's translation from the portal's resource bundle.
928             *
929             * @param  locale the key's locale
930             * @param  key the translation key
931             * @param  defaultValue the value to return if there is no matching
932             *         translation
933             * @return the key's translation, or the default value if the translation is
934             *         unavailable
935             */
936            @Override
937            public String get(Locale locale, String key, String defaultValue) {
938                    if (PropsValues.TRANSLATIONS_DISABLED) {
939                            return key;
940                    }
941    
942                    if ((locale == null) || (key == null)) {
943                            return defaultValue;
944                    }
945    
946                    String value = LanguageResources.getMessage(locale, key);
947    
948                    if (value != null) {
949                            return LanguageResources.fixValue(value);
950                    }
951    
952                    if (value == null) {
953                            if ((key.length() > 0) &&
954                                    (key.charAt(key.length() - 1) == CharPool.CLOSE_BRACKET)) {
955    
956                                    int pos = key.lastIndexOf(CharPool.OPEN_BRACKET);
957    
958                                    if (pos != -1) {
959                                            key = key.substring(0, pos);
960    
961                                            return get(locale, key, defaultValue);
962                                    }
963                            }
964                    }
965    
966                    return defaultValue;
967            }
968    
969            /**
970             * Returns the key's translation from the resource bundle.
971             *
972             * @param  resourceBundle the requested key's resource bundle
973             * @param  key the translation key
974             * @return the key's translation
975             */
976            @Override
977            public String get(ResourceBundle resourceBundle, String key) {
978                    return get(resourceBundle, key, key);
979            }
980    
981            /**
982             * Returns the key's translation from the resource bundle.
983             *
984             * @param  resourceBundle the requested key's resource bundle
985             * @param  key the translation key
986             * @param  defaultValue the value to return if there is no matching
987             *         translation
988             * @return the key's translation, or the default value if the translation is
989             *         unavailable
990             */
991            @Override
992            public String get(
993                    ResourceBundle resourceBundle, String key, String defaultValue) {
994    
995                    String value = _get(resourceBundle, key);
996    
997                    if (value != null) {
998                            return value;
999                    }
1000    
1001                    return defaultValue;
1002            }
1003    
1004            /**
1005             * Returns the locales configured for the portal. Locales can be configured
1006             * in <code>portal.properties</code> using the <code>locales</code> and
1007             * <code>locales.enabled</code> keys.
1008             *
1009             * @return the locales configured for the portal
1010             */
1011            @Override
1012            public Set<Locale> getAvailableLocales() {
1013                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1014    
1015                    return companyLocalesBag.getAvailableLocales();
1016            }
1017    
1018            @Override
1019            public Set<Locale> getAvailableLocales(long groupId) {
1020                    if (groupId <= 0) {
1021                            return getAvailableLocales();
1022                    }
1023    
1024                    try {
1025                            if (isInheritLocales(groupId)) {
1026                                    return getAvailableLocales();
1027                            }
1028                    }
1029                    catch (Exception e) {
1030                    }
1031    
1032                    Map<String, Locale> groupLanguageIdLocalesMap =
1033                            _getGroupLanguageIdLocalesMap(groupId);
1034    
1035                    return new HashSet<>(groupLanguageIdLocalesMap.values());
1036            }
1037    
1038            @Override
1039            public String getBCP47LanguageId(HttpServletRequest request) {
1040                    Locale locale = PortalUtil.getLocale(request);
1041    
1042                    return getBCP47LanguageId(locale);
1043            }
1044    
1045            @Override
1046            public String getBCP47LanguageId(Locale locale) {
1047                    return LocaleUtil.toBCP47LanguageId(locale);
1048            }
1049    
1050            @Override
1051            public String getBCP47LanguageId(PortletRequest portletRequest) {
1052                    Locale locale = PortalUtil.getLocale(portletRequest);
1053    
1054                    return getBCP47LanguageId(locale);
1055            }
1056    
1057            /**
1058             * Returns the language ID that the request is served with. The language ID
1059             * is returned as a language code (e.g. <code>en</code>) or a specific
1060             * variant (e.g. <code>en_GB</code>).
1061             *
1062             * @param  request the request used to determine the language ID
1063             * @return the language ID that the request is served with
1064             */
1065            @Override
1066            public String getLanguageId(HttpServletRequest request) {
1067                    String languageId = ParamUtil.getString(request, "languageId");
1068    
1069                    if (Validator.isNotNull(languageId)) {
1070                            CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1071    
1072                            if (companyLocalesBag.containsLanguageCode(languageId) ||
1073                                    companyLocalesBag.containsLanguageId(languageId)) {
1074    
1075                                    return languageId;
1076                            }
1077                    }
1078    
1079                    Locale locale = PortalUtil.getLocale(request);
1080    
1081                    return getLanguageId(locale);
1082            }
1083    
1084            /**
1085             * Returns the language ID from the locale. The language ID is returned as a
1086             * language code (e.g. <code>en</code>) or a specific variant (e.g.
1087             * <code>en_GB</code>).
1088             *
1089             * @param  locale the locale used to determine the language ID
1090             * @return the language ID from the locale
1091             */
1092            @Override
1093            public String getLanguageId(Locale locale) {
1094                    return LocaleUtil.toLanguageId(locale);
1095            }
1096    
1097            /**
1098             * Returns the language ID that the {@link PortletRequest} is served with.
1099             * The language ID is returned as a language code (e.g. <code>en</code>) or
1100             * a specific variant (e.g. <code>en_GB</code>).
1101             *
1102             * @param  portletRequest the portlet request used to determine the language
1103             *         ID
1104             * @return the language ID that the portlet request is served with
1105             */
1106            @Override
1107            public String getLanguageId(PortletRequest portletRequest) {
1108                    HttpServletRequest request = PortalUtil.getHttpServletRequest(
1109                            portletRequest);
1110    
1111                    return getLanguageId(request);
1112            }
1113    
1114            @Override
1115            public Locale getLocale(long groupId, String languageCode) {
1116                    Map<String, Locale> groupLanguageCodeLocalesMap =
1117                            _getGroupLanguageCodeLocalesMap(groupId);
1118    
1119                    return groupLanguageCodeLocalesMap.get(languageCode);
1120            }
1121    
1122            /**
1123             * Returns the locale associated with the language code.
1124             *
1125             * @param  languageCode the code representation of a language (e.g.
1126             *         <code>en</code> and <code>en_GB</code>)
1127             * @return the locale associated with the language code
1128             */
1129            @Override
1130            public Locale getLocale(String languageCode) {
1131                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1132    
1133                    return companyLocalesBag.getByLanguageCode(languageCode);
1134            }
1135    
1136            @Override
1137            public Set<Locale> getSupportedLocales() {
1138                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1139    
1140                    return companyLocalesBag._supportedLocalesSet;
1141            }
1142    
1143            /**
1144             * Returns an exact localized description of the time interval (in
1145             * milliseconds) in the largest unit possible.
1146             *
1147             * <p>
1148             * For example, the following time intervals would be converted to the
1149             * following time descriptions, using the English locale:
1150             * </p>
1151             *
1152             * <ul>
1153             * <li>
1154             * 1000 = 1 Second
1155             * </li>
1156             * <li>
1157             * 1001 = 1001 Milliseconds
1158             * </li>
1159             * <li>
1160             * 86400000 = 1 Day
1161             * </li>
1162             * <li>
1163             * 86401000 = 86401 Seconds
1164             * </li>
1165             * </ul>
1166             *
1167             * @param  request the request used to determine the current locale
1168             * @param  milliseconds the time interval in milliseconds to describe
1169             * @return an exact localized description of the time interval in the
1170             *         largest unit possible
1171             */
1172            @Override
1173            public String getTimeDescription(
1174                    HttpServletRequest request, long milliseconds) {
1175    
1176                    return getTimeDescription(request, milliseconds, false);
1177            }
1178    
1179            /**
1180             * Returns an approximate or exact localized description of the time
1181             * interval (in milliseconds) in the largest unit possible.
1182             *
1183             * <p>
1184             * Approximate descriptions round the time to the largest possible unit and
1185             * ignores the rest. For example, using the English locale:
1186             * </p>
1187             *
1188             * <ul>
1189             * <li>
1190             * Any time interval 1000-1999 = 1 Second
1191             * </li>
1192             * <li>
1193             * Any time interval 86400000-172799999 = 1 Day
1194             * </li>
1195             * </ul>
1196             *
1197             * <p>
1198             * Otherwise, exact descriptions would follow a similar conversion pattern
1199             * as below:
1200             * </p>
1201             *
1202             * <ul>
1203             * <li>
1204             * 1000 = 1 Second
1205             * </li>
1206             * <li>
1207             * 1001 = 1001 Milliseconds
1208             * </li>
1209             * <li>
1210             * 86400000 = 1 Day
1211             * </li>
1212             * <li>
1213             * 86401000 = 86401 Seconds
1214             * </li>
1215             * </ul>
1216             *
1217             * @param  request the request used to determine the current locale
1218             * @param  milliseconds the time interval in milliseconds to describe
1219             * @param  approximate whether the time description is approximate
1220             * @return a localized description of the time interval in the largest unit
1221             *         possible
1222             */
1223            @Override
1224            public String getTimeDescription(
1225                    HttpServletRequest request, long milliseconds, boolean approximate) {
1226    
1227                    String description = Time.getDescription(milliseconds, approximate);
1228    
1229                    String value = null;
1230    
1231                    try {
1232                            int pos = description.indexOf(CharPool.SPACE);
1233    
1234                            String x = description.substring(0, pos);
1235    
1236                            value = x.concat(StringPool.SPACE).concat(
1237                                    get(
1238                                            request,
1239                                            StringUtil.toLowerCase(
1240                                                    description.substring(pos + 1, description.length()))));
1241                    }
1242                    catch (Exception e) {
1243                            if (_log.isWarnEnabled()) {
1244                                    _log.warn(e, e);
1245                            }
1246                    }
1247    
1248                    return value;
1249            }
1250    
1251            /**
1252             * Returns an exact localized description of the time interval (in
1253             * milliseconds) in the largest unit possible.
1254             *
1255             * <p>
1256             * For example, the following time intervals would be converted to the
1257             * following time descriptions, using the English locale:
1258             * </p>
1259             *
1260             * <ul>
1261             * <li>
1262             * 1000 = 1 Second
1263             * </li>
1264             * <li>
1265             * 1001 = 1001 Milliseconds
1266             * </li>
1267             * <li>
1268             * 86400000 = 1 Day
1269             * </li>
1270             * <li>
1271             * 86401000 = 86401 Seconds
1272             * </li>
1273             * </ul>
1274             *
1275             * @param  request the request used to determine the current locale
1276             * @param  milliseconds the time interval in milliseconds to describe
1277             * @return an exact localized description of the time interval in the
1278             *         largest unit possible
1279             */
1280            @Override
1281            public String getTimeDescription(
1282                    HttpServletRequest request, Long milliseconds) {
1283    
1284                    return getTimeDescription(request, milliseconds.longValue());
1285            }
1286    
1287            /**
1288             * Returns an exact localized description of the time interval (in
1289             * milliseconds) in the largest unit possible.
1290             *
1291             * <p>
1292             * For example, the following time intervals would be converted to the
1293             * following time descriptions, using the English locale:
1294             * </p>
1295             *
1296             * <ul>
1297             * <li>
1298             * 1000 = 1 Second
1299             * </li>
1300             * <li>
1301             * 1001 = 1001 Milliseconds
1302             * </li>
1303             * <li>
1304             * 86400000 = 1 Day
1305             * </li>
1306             * <li>
1307             * 86401000 = 86401 Seconds
1308             * </li>
1309             * </ul>
1310             *
1311             * @param  locale the locale used to determine the language
1312             * @param  milliseconds the time interval in milliseconds to describe
1313             * @return an exact localized description of the time interval in the
1314             *         largest unit possible
1315             */
1316            @Override
1317            public String getTimeDescription(Locale locale, long milliseconds) {
1318                    return getTimeDescription(locale, milliseconds, false);
1319            }
1320    
1321            /**
1322             * Returns an approximate or exact localized description of the time
1323             * interval (in milliseconds) in the largest unit possible.
1324             *
1325             * <p>
1326             * Approximate descriptions round the time to the largest possible unit and
1327             * ignores the rest. For example, using the English locale:
1328             * </p>
1329             *
1330             * <ul>
1331             * <li>
1332             * Any time interval 1000-1999 = 1 Second
1333             * </li>
1334             * <li>
1335             * Any time interval 86400000-172799999 = 1 Day
1336             * </li>
1337             * </ul>
1338             *
1339             * <p>
1340             * Otherwise, exact descriptions would follow a similar conversion pattern
1341             * as below:
1342             * </p>
1343             *
1344             * <ul>
1345             * <li>
1346             * 1000 = 1 Second
1347             * </li>
1348             * <li>
1349             * 1001 = 1001 Milliseconds
1350             * </li>
1351             * <li>
1352             * 86400000 = 1 Day
1353             * </li>
1354             * <li>
1355             * 86401000 = 86401 Seconds
1356             * </li>
1357             * </ul>
1358             *
1359             * @param  locale the locale used to determine the language
1360             * @param  milliseconds the time interval in milliseconds to describe
1361             * @param  approximate whether the time description is approximate
1362             * @return a localized description of the time interval in the largest unit
1363             *         possible
1364             */
1365            @Override
1366            public String getTimeDescription(
1367                    Locale locale, long milliseconds, boolean approximate) {
1368    
1369                    String description = Time.getDescription(milliseconds, approximate);
1370    
1371                    String value = null;
1372    
1373                    try {
1374                            int pos = description.indexOf(CharPool.SPACE);
1375    
1376                            String x = description.substring(0, pos);
1377    
1378                            value = x.concat(StringPool.SPACE).concat(
1379                                    get(
1380                                            locale,
1381                                            StringUtil.toLowerCase(
1382                                                    description.substring(pos + 1, description.length()))));
1383                    }
1384                    catch (Exception e) {
1385                            if (_log.isWarnEnabled()) {
1386                                    _log.warn(e, e);
1387                            }
1388                    }
1389    
1390                    return value;
1391            }
1392    
1393            /**
1394             * Returns an exact localized description of the time interval (in
1395             * milliseconds) in the largest unit possible.
1396             *
1397             * <p>
1398             * For example, the following time intervals would be converted to the
1399             * following time descriptions, using the English locale:
1400             * </p>
1401             *
1402             * <ul>
1403             * <li>
1404             * 1000 = 1 Second
1405             * </li>
1406             * <li>
1407             * 1001 = 1001 Milliseconds
1408             * </li>
1409             * <li>
1410             * 86400000 = 1 Day
1411             * </li>
1412             * <li>
1413             * 86401000 = 86401 Seconds
1414             * </li>
1415             * </ul>
1416             *
1417             * @param  locale the locale used to determine the language
1418             * @param  milliseconds the time interval in milliseconds to describe
1419             * @return an exact localized description of the time interval in the
1420             *         largest unit possible
1421             */
1422            @Override
1423            public String getTimeDescription(Locale locale, Long milliseconds) {
1424                    return getTimeDescription(locale, milliseconds.longValue());
1425            }
1426    
1427            @Override
1428            public void init() {
1429                    _companyLocalesBags.clear();
1430            }
1431    
1432            /**
1433             * Returns <code>true</code> if the language code is configured to be
1434             * available. Locales can be configured in <code>portal.properties</code>
1435             * using the <code>locales</code> and <code>locales.enabled</code> keys.
1436             *
1437             * @param  languageCode the code representation of a language (e.g.
1438             *         <code>en</code> and <code>en_GB</code>) to search for
1439             * @return <code>true</code> if the language code is configured to be
1440             *         available; <code>false</code> otherwise
1441             */
1442            @Override
1443            public boolean isAvailableLanguageCode(String languageCode) {
1444                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1445    
1446                    return companyLocalesBag.containsLanguageCode(languageCode);
1447            }
1448    
1449            /**
1450             * Returns <code>true</code> if the locale is configured to be available.
1451             * Locales can be configured in <code>portal.properties</code> using the
1452             * <code>locales</code> and <code>locales.enabled</code> keys.
1453             *
1454             * @param  locale the locale to search for
1455             * @return <code>true</code> if the locale is configured to be available;
1456             *         <code>false</code> otherwise
1457             */
1458            @Override
1459            public boolean isAvailableLocale(Locale locale) {
1460                    if (locale == null) {
1461                            return false;
1462                    }
1463    
1464                    return isAvailableLocale(LocaleUtil.toLanguageId(locale));
1465            }
1466    
1467            /**
1468             * Returns <code>true</code> if the locale is configured to be available in
1469             * the group.
1470             *
1471             * @param  groupId the primary key of the group
1472             * @param  locale the locale to search for
1473             * @return <code>true</code> if the locale is configured to be available in
1474             *         the group; <code>false</code> otherwise
1475             */
1476            @Override
1477            public boolean isAvailableLocale(long groupId, Locale locale) {
1478                    if (locale == null) {
1479                            return false;
1480                    }
1481    
1482                    return isAvailableLocale(groupId, LocaleUtil.toLanguageId(locale));
1483            }
1484    
1485            /**
1486             * Returns <code>true</code> if the language ID is configured to be
1487             * available in the group.
1488             *
1489             * @param  groupId the primary key of the group
1490             * @param  languageId the language ID to search for
1491             * @return <code>true</code> if the language ID is configured to be
1492             *         available in the group; <code>false</code> otherwise
1493             */
1494            @Override
1495            public boolean isAvailableLocale(long groupId, String languageId) {
1496                    if (groupId <= 0) {
1497                            return isAvailableLocale(languageId);
1498                    }
1499    
1500                    try {
1501                            if (isInheritLocales(groupId)) {
1502                                    return isAvailableLocale(languageId);
1503                            }
1504                    }
1505                    catch (Exception e) {
1506                    }
1507    
1508                    Map<String, Locale> groupLanguageIdLocalesMap =
1509                            _getGroupLanguageIdLocalesMap(groupId);
1510    
1511                    return groupLanguageIdLocalesMap.containsKey(languageId);
1512            }
1513    
1514            /**
1515             * Returns <code>true</code> if the language ID is configured to be
1516             * available.
1517             *
1518             * @param  languageId the language ID to search for
1519             * @return <code>true</code> if the language ID is configured to be
1520             *         available; <code>false</code> otherwise
1521             */
1522            @Override
1523            public boolean isAvailableLocale(String languageId) {
1524                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1525    
1526                    return companyLocalesBag.containsLanguageId(languageId);
1527            }
1528    
1529            /**
1530             * Returns <code>true</code> if the locale is configured to be a beta
1531             * language.
1532             *
1533             * @param  locale the locale to search for
1534             * @return <code>true</code> if the locale is configured to be a beta
1535             *         language; <code>false</code> otherwise
1536             */
1537            @Override
1538            public boolean isBetaLocale(Locale locale) {
1539                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1540    
1541                    return companyLocalesBag.isBetaLocale(locale);
1542            }
1543    
1544            @Override
1545            public boolean isDuplicateLanguageCode(String languageCode) {
1546                    CompanyLocalesBag companyLocalesBag = _getCompanyLocalesBag();
1547    
1548                    return companyLocalesBag.isDuplicateLanguageCode(languageCode);
1549            }
1550    
1551            @Override
1552            public boolean isInheritLocales(long groupId) throws PortalException {
1553                    Group group = GroupLocalServiceUtil.getGroup(groupId);
1554    
1555                    if (group.isStagingGroup()) {
1556                            group = group.getLiveGroup();
1557                    }
1558    
1559                    if (!group.isSite() || group.isCompany()) {
1560                            return true;
1561                    }
1562    
1563                    return GetterUtil.getBoolean(
1564                            group.getTypeSettingsProperty(
1565                                    GroupConstants.TYPE_SETTINGS_KEY_INHERIT_LOCALES),
1566                            true);
1567            }
1568    
1569            @Override
1570            public String process(
1571                    ResourceBundle resourceBundle, Locale locale, String content) {
1572    
1573                    StringBundler sb = new StringBundler();
1574    
1575                    Matcher matcher = _pattern.matcher(content);
1576    
1577                    int x = 0;
1578    
1579                    while (matcher.find()) {
1580                            int y = matcher.start(0);
1581    
1582                            String key = matcher.group(1);
1583    
1584                            sb.append(content.substring(x, y));
1585                            sb.append(StringPool.APOSTROPHE);
1586    
1587                            String value = get(resourceBundle, key);
1588    
1589                            sb.append(HtmlUtil.escapeJS(value));
1590                            sb.append(StringPool.APOSTROPHE);
1591    
1592                            x = matcher.end(0);
1593                    }
1594    
1595                    sb.append(content.substring(x));
1596    
1597                    return sb.toString();
1598            }
1599    
1600            @Override
1601            public void resetAvailableGroupLocales(long groupId) {
1602                    _resetAvailableGroupLocales(groupId);
1603            }
1604    
1605            @Override
1606            public void resetAvailableLocales(long companyId) {
1607                    _resetAvailableLocales(companyId);
1608            }
1609    
1610            @Override
1611            public void updateCookie(
1612                    HttpServletRequest request, HttpServletResponse response,
1613                    Locale locale) {
1614    
1615                    String languageId = LocaleUtil.toLanguageId(locale);
1616    
1617                    Cookie languageIdCookie = new Cookie(
1618                            CookieKeys.GUEST_LANGUAGE_ID, languageId);
1619    
1620                    languageIdCookie.setPath(StringPool.SLASH);
1621                    languageIdCookie.setMaxAge(CookieKeys.MAX_AGE);
1622    
1623                    CookieKeys.addCookie(request, response, languageIdCookie);
1624            }
1625    
1626            protected MessageFormat decorateMessageFormat(
1627                    HttpServletRequest request, String pattern,
1628                    Object[] formattedArguments) {
1629    
1630                    Locale locale = _getLocale(request);
1631    
1632                    return decorateMessageFormat(locale, pattern, formattedArguments);
1633            }
1634    
1635            protected MessageFormat decorateMessageFormat(
1636                    Locale locale, String pattern, Object[] formattedArguments) {
1637    
1638                    if (locale == null) {
1639                            locale = LocaleUtil.getDefault();
1640                    }
1641    
1642                    MessageFormat messageFormat = new MessageFormat(pattern, locale);
1643    
1644                    for (int i = 0; i < formattedArguments.length; i++) {
1645                            Object formattedArgument = formattedArguments[i];
1646    
1647                            if (formattedArgument instanceof Number) {
1648                                    messageFormat.setFormat(i, NumberFormat.getInstance(locale));
1649                            }
1650                    }
1651    
1652                    return messageFormat;
1653            }
1654    
1655            private static CompanyLocalesBag _getCompanyLocalesBag() {
1656                    Long companyId = CompanyThreadLocal.getCompanyId();
1657    
1658                    CompanyLocalesBag companyLocalesBag = _companyLocalesBags.get(
1659                            companyId);
1660    
1661                    if (companyLocalesBag == null) {
1662                            companyLocalesBag = new CompanyLocalesBag(companyId);
1663    
1664                            _companyLocalesBags.put(companyId, companyLocalesBag);
1665                    }
1666    
1667                    return companyLocalesBag;
1668            }
1669    
1670            private ObjectValuePair<Map<String, Locale>, Map<String, Locale>>
1671                    _createGroupLocales(long groupId) {
1672    
1673                    String[] languageIds = PropsValues.LOCALES_ENABLED;
1674    
1675                    try {
1676                            Group group = GroupLocalServiceUtil.getGroup(groupId);
1677    
1678                            UnicodeProperties typeSettingsProperties =
1679                                    group.getTypeSettingsProperties();
1680    
1681                            languageIds = StringUtil.split(
1682                                    typeSettingsProperties.getProperty(PropsKeys.LOCALES));
1683                    }
1684                    catch (Exception e) {
1685                    }
1686    
1687                    Map<String, Locale> groupLanguageCodeLocalesMap = new HashMap<>();
1688                    Map<String, Locale> groupLanguageIdLocalesMap = new HashMap<>();
1689    
1690                    for (String languageId : languageIds) {
1691                            Locale locale = LocaleUtil.fromLanguageId(languageId, false);
1692    
1693                            String languageCode = languageId;
1694    
1695                            int pos = languageId.indexOf(CharPool.UNDERLINE);
1696    
1697                            if (pos > 0) {
1698                                    languageCode = languageId.substring(0, pos);
1699                            }
1700    
1701                            if (!groupLanguageCodeLocalesMap.containsKey(languageCode)) {
1702                                    groupLanguageCodeLocalesMap.put(languageCode, locale);
1703                            }
1704    
1705                            groupLanguageIdLocalesMap.put(languageId, locale);
1706                    }
1707    
1708                    _groupLanguageCodeLocalesMapMap.put(
1709                            groupId, groupLanguageCodeLocalesMap);
1710                    _groupLanguageIdLocalesMap.put(groupId, groupLanguageIdLocalesMap);
1711    
1712                    return new ObjectValuePair<>(
1713                            groupLanguageCodeLocalesMap, groupLanguageIdLocalesMap);
1714            }
1715    
1716            private String _escapePattern(String pattern) {
1717                    return StringUtil.replace(
1718                            pattern, StringPool.APOSTROPHE, StringPool.DOUBLE_APOSTROPHE);
1719            }
1720    
1721            private String _get(ResourceBundle resourceBundle, String key) {
1722                    if (PropsValues.TRANSLATIONS_DISABLED) {
1723                            return key;
1724                    }
1725    
1726                    if ((resourceBundle == null) || (key == null)) {
1727                            return null;
1728                    }
1729    
1730                    String value = ResourceBundleUtil.getString(resourceBundle, key);
1731    
1732                    if (value != null) {
1733                            return LanguageResources.fixValue(value);
1734                    }
1735    
1736                    if ((key.length() > 0) &&
1737                            (key.charAt(key.length() - 1) == CharPool.CLOSE_BRACKET)) {
1738    
1739                            int pos = key.lastIndexOf(CharPool.OPEN_BRACKET);
1740    
1741                            if (pos != -1) {
1742                                    key = key.substring(0, pos);
1743    
1744                                    return _get(resourceBundle, key);
1745                            }
1746                    }
1747    
1748                    return null;
1749            }
1750    
1751            private Map<String, Locale> _getGroupLanguageCodeLocalesMap(long groupId) {
1752                    Map<String, Locale> groupLanguageCodeLocalesMap =
1753                            _groupLanguageCodeLocalesMapMap.get(groupId);
1754    
1755                    if (groupLanguageCodeLocalesMap == null) {
1756                            ObjectValuePair<Map<String, Locale>, Map<String, Locale>>
1757                                    objectValuePair = _createGroupLocales(groupId);
1758    
1759                            groupLanguageCodeLocalesMap = objectValuePair.getKey();
1760                    }
1761    
1762                    return groupLanguageCodeLocalesMap;
1763            }
1764    
1765            private Map<String, Locale> _getGroupLanguageIdLocalesMap(long groupId) {
1766                    Map<String, Locale> groupLanguageIdLocalesMap =
1767                            _groupLanguageIdLocalesMap.get(groupId);
1768    
1769                    if (groupLanguageIdLocalesMap == null) {
1770                            ObjectValuePair<Map<String, Locale>, Map<String, Locale>>
1771                                    objectValuePair = _createGroupLocales(groupId);
1772    
1773                            groupLanguageIdLocalesMap = objectValuePair.getValue();
1774                    }
1775    
1776                    return groupLanguageIdLocalesMap;
1777            }
1778    
1779            private Locale _getLocale(HttpServletRequest request) {
1780                    Locale locale = null;
1781    
1782                    ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
1783                            WebKeys.THEME_DISPLAY);
1784    
1785                    if (themeDisplay != null) {
1786                            locale = themeDisplay.getLocale();
1787                    }
1788                    else {
1789                            locale = request.getLocale();
1790    
1791                            if (!isAvailableLocale(locale)) {
1792                                    locale = LocaleUtil.getDefault();
1793                            }
1794                    }
1795    
1796                    return locale;
1797            }
1798    
1799            private void _resetAvailableGroupLocales(long groupId) {
1800                    _groupLanguageCodeLocalesMapMap.remove(groupId);
1801                    _groupLanguageIdLocalesMap.remove(groupId);
1802            }
1803    
1804            private void _resetAvailableLocales(long companyId) {
1805                    _portalCache.remove(companyId);
1806            }
1807    
1808            private static final Log _log = LogFactoryUtil.getLog(LanguageImpl.class);
1809    
1810            private static final Map<Long, CompanyLocalesBag> _companyLocalesBags =
1811                    new ConcurrentHashMap<>();
1812            private static final Pattern _pattern = Pattern.compile(
1813                    "Liferay\\.Language\\.get\\([\"']([^)]+)[\"']\\)");
1814            private static PortalCache<Long, Serializable> _portalCache;
1815    
1816            private final Map<Long, Map<String, Locale>>
1817                    _groupLanguageCodeLocalesMapMap = new ConcurrentHashMap<>();
1818            private final Map<Long, Map<String, Locale>> _groupLanguageIdLocalesMap =
1819                    new ConcurrentHashMap<>();
1820    
1821            private static class CompanyLocalesBag implements Serializable {
1822    
1823                    public boolean containsLanguageCode(String languageCode) {
1824                            return _languageCodeLocalesMap.containsKey(languageCode);
1825                    }
1826    
1827                    public boolean containsLanguageId(String languageId) {
1828                            return _languageIdLocalesMap.containsKey(languageId);
1829                    }
1830    
1831                    public Set<Locale> getAvailableLocales() {
1832                            return new HashSet<>(_languageIdLocalesMap.values());
1833                    }
1834    
1835                    public Locale getByLanguageCode(String languageCode) {
1836                            return _languageCodeLocalesMap.get(languageCode);
1837                    }
1838    
1839                    public boolean isBetaLocale(Locale locale) {
1840                            return _localesBetaSet.contains(locale);
1841                    }
1842    
1843                    public boolean isDuplicateLanguageCode(String languageCode) {
1844                            return _duplicateLanguageCodes.contains(languageCode);
1845                    }
1846    
1847                    private CompanyLocalesBag(long companyId) {
1848                            String[] languageIds = PropsValues.LOCALES;
1849    
1850                            if (companyId != CompanyConstants.SYSTEM) {
1851                                    try {
1852                                            languageIds = PrefsPropsUtil.getStringArray(
1853                                                    companyId, PropsKeys.LOCALES, StringPool.COMMA,
1854                                                    PropsValues.LOCALES_ENABLED);
1855                                    }
1856                                    catch (SystemException se) {
1857                                            languageIds = PropsValues.LOCALES_ENABLED;
1858                                    }
1859                            }
1860    
1861                            for (String languageId : languageIds) {
1862                                    Locale locale = LocaleUtil.fromLanguageId(languageId, false);
1863    
1864                                    String languageCode = languageId;
1865    
1866                                    int pos = languageId.indexOf(CharPool.UNDERLINE);
1867    
1868                                    if (pos > 0) {
1869                                            languageCode = languageId.substring(0, pos);
1870                                    }
1871    
1872                                    if (_languageCodeLocalesMap.containsKey(languageCode)) {
1873                                            _duplicateLanguageCodes.add(languageCode);
1874                                    }
1875                                    else {
1876                                            _languageCodeLocalesMap.put(languageCode, locale);
1877                                    }
1878    
1879                                    _languageIdLocalesMap.put(languageId, locale);
1880                            }
1881    
1882                            for (String languageId : PropsValues.LOCALES_BETA) {
1883                                    _localesBetaSet.add(
1884                                            LocaleUtil.fromLanguageId(languageId, false));
1885                            }
1886    
1887                            _supportedLocalesSet = new HashSet<>(
1888                                    _languageIdLocalesMap.values());
1889    
1890                            _supportedLocalesSet.removeAll(_localesBetaSet);
1891                    }
1892    
1893                    private final Set<String> _duplicateLanguageCodes = new HashSet<>();
1894                    private final Map<String, Locale> _languageCodeLocalesMap =
1895                            new HashMap<>();
1896                    private final Map<String, Locale> _languageIdLocalesMap =
1897                            new HashMap<>();
1898                    private final Set<Locale> _localesBetaSet = new HashSet<>();
1899                    private final Set<Locale> _supportedLocalesSet;
1900    
1901            }
1902    
1903    }