/* * Copyright 2011 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.i18n.server; import com.google.gwt.i18n.client.Messages.Example; import com.google.gwt.i18n.client.Messages.PluralCount; import com.google.gwt.i18n.client.Messages.Select; import com.google.gwt.i18n.server.MessageFormatUtils.MessageStyle; import com.google.gwt.i18n.shared.GwtLocale; import java.io.IOException; import java.io.PrintWriter; import java.util.List; /** * Factory for GWT properties file format. */ public class PropertyCatalogFactory implements MessageCatalogFactory { // @VisibleForTesting static final String SELECTOR_BOILERPLATE_1 = "# Lines of the form" + " key[form|form]=msg are for alternate forms of"; static final String SELECTOR_BOILERPLATE_2 = "# the message according to" + " Plural Count and Selector entries above."; static final String STRINGMAP_BOILERPLATE_1 = "# Lines of the form" + " key[entry]=msg are for individual entries, and"; static final String STRINGMAP_BOILERPLATE_2 = "# the line without [] lists" + " the entries, separated by commas."; private static class PropertiesWriter extends DefaultVisitor implements Writer { private static String stringJoin(String joiner, String... values) { StringBuilder buf = new StringBuilder(); boolean needsJoiner = false; for (String value : values) { if (needsJoiner) { buf.append(joiner); } else { needsJoiner = true; } buf.append(value); } return buf.toString(); } private final PrintWriter writer; private String baseKey; public PropertiesWriter(PrintWriter writer) { this.writer = writer; } public void close() throws IOException { writer.close(); } @Override public void endMessage(Message msg, MessageTranslation trans) { baseKey = null; } public MessageInterfaceVisitor visitClass() { return this; } @Override public MessageVisitor visitMessage(Message msg, MessageTranslation trans) { writer.println(); String description = msg.getDescription(); if (description != null) { writer.println("# Description: " + description); } String meaning = msg.getMeaning(); if (meaning != null) { writer.println("# Meaning: " + meaning); } List<Parameter> params = msg.getParameters(); for (int i = 0; i < params.size(); ++i) { Parameter param = params.get(i); writer.print("# " + i + " - " + param.getName()); if (param.isAnnotationPresent(PluralCount.class)) { writer.print(", Plural Count"); } if (param.isAnnotationPresent(Select.class)) { writer.print(", Selector"); } if (param.isAnnotationPresent(Example.class)) { Example exampleAnnot = param.getAnnotation(Example.class); writer.print(", Example: " + exampleAnnot.value()); } writer.println(); } int[] selectorIndices = msg.getSelectorParameterIndices(); if (selectorIndices.length > 0) { if (selectorIndices[0] >= 0) { writer.println(SELECTOR_BOILERPLATE_1); writer.println(SELECTOR_BOILERPLATE_2); } else { writer.println(STRINGMAP_BOILERPLATE_1); writer.println(STRINGMAP_BOILERPLATE_2); } } baseKey = quoteKey(msg.getKey()); writer.println(baseKey + "=" + propertiesMessage(msg.getMessageStyle(), msg.getDefaultMessage())); return this; } @Override public void visitMessageInterface(MessageInterface msgIntf, GwtLocale sourceLocale) { writer.println("# Messages from " + msgIntf.getQualifiedName()); writer.println("# Source locale " + sourceLocale); } @Override public void visitTranslation(String[] formNames, boolean isDefault, MessageStyle style, String msg) { if (isDefault) { // default message is processed in processDefaultMessageBefore return; } if (msg == null) { msg = ""; } String key = baseKey; key += "[" + stringJoin("|", formNames) + "]"; writer.println(key + "=" + propertiesMessage(style, msg)); } private String propertiesMessage(MessageStyle style, String msg) { if (msg == null) { // TODO(jat): is this the right thing to do if no translation was found? return ""; } // TODO(jat): translate so property files have consistent quoting rules? return quoteValue(msg); } /** * Quote keys for use in a properties file. * * In addition to the usual quoting, all spaces are backslash-quoted. * * @param str key to quote * @return quoted key */ private String quoteKey(String str) { str = str.replace("\\", "\\\\"); str = str.replace(" ", "\\ "); return quoteSpecial(str); } /** * Quote strings for use in a properties file. * * @param str string to quote * @return quoted string */ private String quoteSpecial(String str) { return str.replaceAll("([\f\t\n\r$!=:#])", "\\\\$1"); } /** * Quote values for use in a properties file. * * In addition to the usual quoting, leading spaces are backslash-quoted. * * @param str value to quote * @return quoted value */ private String quoteValue(String str) { str = str.replace("\\", "\\\\"); if (str.startsWith(" ")) { int n = 0; while (n < str.length() && str.charAt(n) == ' ') { n++; } str = str.substring(n); while (n-- > 0) { str = "\\ " + str; } } return quoteSpecial(str); } } public String getExtension() { return ".properties"; } public Writer getWriter(Context context, String fileName) { PrintWriter pw = context.createTextFile(fileName, "UTF-8"); if (pw == null) { return null; } PropertiesWriter writer = new PropertiesWriter(pw); return writer; } }