Play Framework 1.2.7学习笔记之Messages(国际化)

本文详细介绍了 Play Framework 在国际化信息的加载和使用过程,包括初始化过程、读取国际化信息的方法以及控制层如何调用。强调了在不同场景下如何通过线程局部变量 Lang 类来获取当前应用的本地化设置,并通过 Messages 类来实现国际化信息的读取与格式化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.play framework中国际化信息大致分为两个步骤:一是从文件中读取国际化信息存放到类的static变量中。二是,根据request中的locale信息,从static变量中读取key所对应的value。涉及到的类基本如下


2.初始化过程

2.1play framemwork启动时(如果是dev模式,延迟到第一次访问时),读取play.plugins文件中的所有插件(如下图):


依次调用每个插件的onApplicationStart方法,MessagePlugin类的onApplicationStart方法中做了两件事:

一是从conf文件夹下的message文件(可以是多个:play根目录及各个module中)中读取默认信息放到Messages类的静态变量defaults属性中。

二是读取play支持的local(可以有多个:从Play.langs中读取),从conf文件夹下的message.local文件(local可以是zh、fr等等,文件也可以是多个:play根目录及各个module中)中读取local对应的国际化信息到Messages类的静态变量locals中。

@Override
    public void onApplicationStart() {
    	//初始化默认国际化信息到defaults中
        Messages.defaults = new Properties();
        try {
            FileInputStream is = new FileInputStream(new File(Play.frameworkPath, "resources/messages"));
            Messages.defaults.putAll(IO.readUtf8Properties(is));
        } catch(Exception e) {
            Logger.warn("Defaults messsages file missing");
        }
        for(VirtualFile module : Play.modules.values()) {
            VirtualFile messages = module.child("conf/messages");
            if(messages != null && messages.exists()) {
                Messages.defaults.putAll(read(messages)); 
            }
        }
        VirtualFile appDM = Play.getVirtualFile("conf/messages");
        if(appDM != null && appDM.exists()) {
            Messages.defaults.putAll(read(appDM));
        }
        //初始化locale对应的国际化信息到locals中
        //Play.langs在这之前已经初始化了,从配置项application.langs中获取,默认为空集合
        //Play.modules在这之前已经初始化了,从配置文件play.plugins文件中读取
        for (String locale : Play.langs) {
            Properties properties = new Properties();
            for(VirtualFile module : Play.modules.values()) {
                VirtualFile messages = module.child("conf/messages." + locale);
                if(messages != null && messages.exists()) {
                    properties.putAll(read(messages)); 
                }
            }
            VirtualFile appM = Play.getVirtualFile("conf/messages." + locale);
            if(appM != null && appM.exists()) {
                properties.putAll(read(appM));
            } else {
                Logger.warn("Messages file missing for locale %s", locale);
            }     
            Messages.locales.put(locale, properties);
        }
        lastLoading = System.currentTimeMillis();
    }
2.2Messages类中还有一个方法detectChange,这个方法会在DEV模式下被调用。作用就是检查各个国际化文件(包括根目录及各个module下的conf文件夹下的message、message.zh文件)在onApplicationStart上次调用后有没有修改过。如果修改过,重新调用onApplicationStart方法。

@Override
    public void detectChange() {
        if (Play.getVirtualFile("conf/messages")!=null && Play.getVirtualFile("conf/messages").lastModified() > lastLoading) {
            onApplicationStart();
            return;
        }
        for(VirtualFile module : Play.modules.values()) {
            if(module.child("conf/messages") != null && module.child("conf/messages").exists() && module.child("conf/messages").lastModified() > lastLoading) {
                onApplicationStart();
                return;
            }
        }
        for (String locale : Play.langs) {
            if (Play.getVirtualFile("conf/messages." + locale)!=null && Play.getVirtualFile("conf/messages." + locale).lastModified() > lastLoading) {
                onApplicationStart();
                return;
            }
            for(VirtualFile module : Play.modules.values()) {
                if(module.child("conf/messages."+locale) != null && module.child("conf/messages."+locale).exists() && module.child("conf/messages."+locale).lastModified() > lastLoading) {
                    onApplicationStart();
                    return;
                }
            }
        }

    }
3.读取国际化信息。这个过程需要我们自己写代码实现,在控制层中调用Messages的get方法即可。

/**
     * Given a message code, translate it using current locale.
     * If there is no message in the current locale for the given key, the key
     * is returned.
     * 
     * @param key the message code
     * @param args optional message format arguments
     * @return translated message
     */
    public static String get(Object key, Object... args) {
        return getMessage(Lang.get(), key, args);
    }
3.1get方法中调用了getMessage方法,这个方法的有三个参数:第一个参数是locale,第二个参数是国际化key,第三个参数是用于格式化的args。而locale参数通过Lang.get()方法获取。

Lang类使用了ThreadLocal模式。

如果当前线程有locale,直接返回。

如果没有,获取当前线程的request对象。

如果request不存在(表示没有请求进行),则调用setDefaultLocale方法,从Play.langs中取第一个值作为默认值并返回。

如果request存在,则调用resolvefrom方法设置locale。这个方法先从request的cookie中获取locale进行设值;如果cookei中没有locale信息,则从request的请求头的accept-language中获取信息进行设置。

Lang类的get方法如下:

public static ThreadLocal<String> current = new ThreadLocal<String>();

    /**
     * Retrieve the current language or null
     * @return The current language (fr, ja, it ...) or null
     */
    public static String get() {
    	//从当前线程中获取
        String locale = current.get();
        //如果不存在,则从request中解析
        if (locale == null) {
            // don't have current locale for this request - must try to resolve it
            Http.Request currentRequest = Http.Request.current();
            //如果request存在(表示有浏览器访问应用)
            if (currentRequest!=null) {
                // we have a current request - lets try to resolve language from it
                resolvefrom( currentRequest );
            } else {
            	//当没有request时设置默认值
                // don't have current request - just use default
                setDefaultLocale();
            }
            // get the picked locale
            locale = current.get();
        }
        return locale;
    }
Lang类的resolvefrom方法:

/**
     * Guess the language for current request in the following order:
     * <ol>
     * <li>if a <b>PLAY_LANG</b> cookie is set, use this value</li>
     * <li>if <b>Accept-Language</b> header is set, use it only if the Play! application allows it.<br/>supported language may be defined in application configuration, eg : <em>play.langs=fr,en,de)</em></li>
     * <li>otherwise, server's locale language is assumed
     * </ol>
     * @param request
     */
    private static void resolvefrom(Request request) {
        // Check a cookie
        String cn = Play.configuration.getProperty("application.lang.cookie", "PLAY_LANG");
        if (request.cookies.containsKey(cn)) {
        	//从cookie中获取locale
            String localeFromCookie = request.cookies.get(cn).value;
            if (localeFromCookie != null && localeFromCookie.trim().length()>0) {
            	//如果cookie中的locale信息在Play.langs中存在,直接返回locale
                if (set(localeFromCookie)) {
                    // we're using locale from cookie
                    return;
                }
                // could not use locale from cookie - clear the locale-cookie
                Response.current().setCookie(cn, "");

            }

        }
        //从request的headers中读取accept-language值,并且从Play.langs中找出匹配值。如果未找到匹配中,返回null
        String closestLocaleMatch = findClosestMatch(request.acceptLanguage());
        if ( closestLocaleMatch != null ) {
            set(closestLocaleMatch);
        } else {
            // Did not find anything - use default
        	//最坏情况下,设置默认值
            setDefaultLocale();
        }

    }
3.2Messages的getMessage方法:

首先遍历所有的插件并调用getMessage方法,得到message。

如果message存在直接返回。

如果message不存在,从静态变量locales中获取对应locale和对应key的值value。调用formatString(value,args)方法进行格式化并返回。

public static String getMessage(String locale, Object key, Object... args) {
        // Check if there is a plugin that handles translation
    	//遍历所有插件,调用getMessage方法
        String message = Play.pluginCollection.getMessage(locale, key, args);

        if(message != null) {
        	//如果message存在,直接返回
            return message;
        }
    
        String value = null;
        if( key == null ) {
            return "";
        }
        if (locales.containsKey(locale)) {
            value = locales.get(locale).getProperty(key.toString());
        }
        if (value == null) {
            value = defaults.getProperty(key.toString());
        }
        if (value == null) {
            value = key.toString();
        }
        //格式化value
        return formatString(value, args);
    }
3.3Messages类的formatString方法

TODO:使用了两个正则表达式进行格式化,对正则表达式掌握不好,未看懂,求指导

static Pattern recursive = Pattern.compile("&\\{(.*?)\\}");
    
    public static String formatString(String value, Object... args) {
        String message = String.format(value, coolStuff(value, args));
        Matcher matcher = recursive.matcher(message);
        StringBuffer sb = new StringBuffer();
        while(matcher.find()) {
            matcher.appendReplacement(sb, get(matcher.group(1)));
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    static Pattern formatterPattern = Pattern.compile("%((\\d+)\\$)?([-#+ 0,(]+)?(\\d+)?([.]\\d+)?([bBhHsScCdoxXeEfgGaAtT])");
    
    @SuppressWarnings("unchecked")
    static Object[] coolStuff(String pattern, Object[] args) {
    	// when invoked with a null argument we get a null args instead of an array with a null value.

    	if(args == null)
    		return NO_ARGS;

        Class<? extends Number>[] conversions = new Class[args.length];

        Matcher matcher = formatterPattern.matcher(pattern);
        int incrementalPosition = 1;
        while(matcher.find()) {
            String conversion = matcher.group(6);
            Integer position;
            if(matcher.group(2) == null) {
                position = incrementalPosition++;
            } else {
                position = Integer.parseInt(matcher.group(2));
            }
            if(conversion.equals("d") && position <= conversions.length) {
                conversions[position-1] = Long.class;
            }
            if(conversion.equals("f") && position <= conversions.length) {
                conversions[position-1] = Double.class;
            }
        }

        Object[] result = new Object[args.length];
        for(int i=0; i < args.length; i++) {
            if(args[i] == null) {
                continue;
            }
            if(conversions[i] == null) {
                result[i] = args[i];
            } else {
                try {
                    // TODO: I think we need to type of direct bind -> primitive and object binder
                    result[i] = Binder.directBind(null, args[i] + "", conversions[i], null);
                } catch(Exception e) {
                    // Ignore
                    result[i] = null;
                }
            }
        }
        return result;
    }
4.小结

mvc框架的国际化信息过程基本一致。首先是加载国际化文件到静态变量中。然后就是根据locale和key从静态变量中取值的过程。play在实现的具体细节上还有一些没有弄明白,求指导。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值