Add support for i18n annotations, including plural forms.

Patch by: jat
Review by: bruce, jgw, mmendez, rdayal, shanjian, bbavar



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2116 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/doc/build.xml b/doc/build.xml
index 343e641..344aa28 100644
--- a/doc/build.xml
+++ b/doc/build.xml
@@ -9,7 +9,8 @@
 	<!-- Platform shouldn't matter here, just picking one -->
 	<property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev-linux.jar" />
 
-	<property name="USER_PKGS" value="com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.typeinfo;com.google.gwt.i18n.client;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.benchmarks.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.server.rpc;com.google.gwt.xml.client;com.google.gwt.http.client" />
+	<property name="USER_PKGS"
+          value="com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.typeinfo;com.google.gwt.i18n.client;com.google.gwt.i18n.rebind.format;com.google.gwt.i18n.rebind.keygen;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.server.rpc;com.google.gwt.xml.client;com.google.gwt.http.client;com.google.gwt.junit.viewer.client" />
 	<property name="LANG_PKGS" value="java.lang;java.lang.annotation;java.util;java.io" />
 
 	<!--
diff --git a/user/javadoc/com/example/foo/Foo.gwt.xml b/user/javadoc/com/example/foo/Foo.gwt.xml
index 6fee914..c38a343 100644
--- a/user/javadoc/com/example/foo/Foo.gwt.xml
+++ b/user/javadoc/com/example/foo/Foo.gwt.xml
@@ -1,17 +1,3 @@
-<!--                                                                        -->
-<!-- Copyright 2007 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   -->
-<!-- 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. License for the specific language governing permissions and   -->
-<!-- limitations under the License.                                         -->
-
 <module>
   <!-- Module com.example.foo.Foo -->
 
diff --git a/user/javadoc/com/google/gwt/examples/Benchmarks.gwt.xml b/user/javadoc/com/google/gwt/examples/Benchmarks.gwt.xml
index e558de5..b61f09d 100644
--- a/user/javadoc/com/google/gwt/examples/Benchmarks.gwt.xml
+++ b/user/javadoc/com/google/gwt/examples/Benchmarks.gwt.xml
@@ -1,17 +1,3 @@
-<!--                                                                        -->
-<!-- Copyright 2007 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   -->
-<!-- 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. License for the specific language governing permissions and   -->
-<!-- limitations under the License.                                         -->
-
 <module>
   <source path='benchmarks'/>
 </module>
diff --git a/user/javadoc/com/google/gwt/examples/http/GetExample.gwt.xml b/user/javadoc/com/google/gwt/examples/http/GetExample.gwt.xml
index e33a1de..54a7b6b 100644
--- a/user/javadoc/com/google/gwt/examples/http/GetExample.gwt.xml
+++ b/user/javadoc/com/google/gwt/examples/http/GetExample.gwt.xml
@@ -1,17 +1,3 @@
-<!--                                                                        -->
-<!-- Copyright 2007 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   -->
-<!-- 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. License for the specific language governing permissions and   -->
-<!-- limitations under the License.                                         -->
-
 <!-- Types and resources required to support HTTP requests.                 -->
 <!--                                                                        -->
 <module>
diff --git a/user/javadoc/com/google/gwt/examples/http/InheritsExample.gwt.xml b/user/javadoc/com/google/gwt/examples/http/InheritsExample.gwt.xml
index 38b15c3..c0253f4 100644
--- a/user/javadoc/com/google/gwt/examples/http/InheritsExample.gwt.xml
+++ b/user/javadoc/com/google/gwt/examples/http/InheritsExample.gwt.xml
@@ -1,17 +1,3 @@
-<!--                                                                        -->
-<!-- Copyright 2007 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   -->
-<!-- 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. License for the specific language governing permissions and   -->
-<!-- limitations under the License.                                         -->
-
 <module>
   <!-- other inherited modules, such as com.google.gwt.user.User -->
   <inherits name="com.google.gwt.http.HTTP"/>
diff --git a/user/javadoc/com/google/gwt/examples/i18n/AnnotConstants.java b/user/javadoc/com/google/gwt/examples/i18n/AnnotConstants.java
new file mode 100644
index 0000000..a8668c8
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/i18n/AnnotConstants.java
@@ -0,0 +1,34 @@
+// Copyright 2008 Google Inc.  All Rights Reserved.
+package com.google.gwt.examples.i18n;
+
+import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
+
+import java.util.Map;
+
+@DefaultLocale("en_US")
+public interface AnnotConstants extends Constants {
+  int CURRENCY_DECIMALS = 2;
+
+  @DefaultStringValue("Orange")
+  @Meaning("the color")
+  String orange();
+
+  @DefaultStringValue("Red")
+  String red();
+
+  @DefaultIntValue(CURRENCY_DECIMALS)
+  int currencyDecimals();
+  
+  @DefaultIntValue(CURRENCY_DECIMALS * 2)
+  int extraCurrencyDecimals();
+  
+  @DefaultStringArrayValue({"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"})
+  String[] numberNames();
+  
+  @DefaultDoubleValue(3.14159)
+  double pi();
+  
+  @DefaultStringMapValue({"key1", "comma,value", "comma,key", "value2"})
+  Map<String, String> map();
+}
diff --git a/user/javadoc/com/google/gwt/examples/i18n/GameStatusMessagesAnnot.java b/user/javadoc/com/google/gwt/examples/i18n/GameStatusMessagesAnnot.java
new file mode 100644
index 0000000..e065e43
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/i18n/GameStatusMessagesAnnot.java
@@ -0,0 +1,20 @@
+package com.google.gwt.examples.i18n;
+
+import com.google.gwt.i18n.client.Messages;
+
+public interface GameStatusMessagesAnnot extends Messages {
+  /**
+   * @param username the name of a player
+   * @param numTurns the number of turns remaining
+   * @return a message specifying the remaining turns for a player
+   */
+  @DefaultMessage("Turns left for player ''{0}'': {1}")
+  String turnsLeft(String username, int numTurns);
+
+  /**
+   * @param numPoints the number of points
+   * @return a message describing the current score for the current player
+   */
+  @DefaultMessage("Current score: {0}")
+  String currentScore(int numPoints);
+}
diff --git a/user/javadoc/com/google/gwt/examples/i18n/InheritsExample.gwt.xml b/user/javadoc/com/google/gwt/examples/i18n/InheritsExample.gwt.xml
index 677dc3f..8c80ff9 100644
--- a/user/javadoc/com/google/gwt/examples/i18n/InheritsExample.gwt.xml
+++ b/user/javadoc/com/google/gwt/examples/i18n/InheritsExample.gwt.xml
@@ -1,17 +1,3 @@
-<!--                                                                        -->
-<!-- Copyright 2007 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   -->
-<!-- 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. License for the specific language governing permissions and   -->
-<!-- limitations under the License.                                         -->
-
 <module>
   <!-- other inherited modules, such as com.google.gwt.user.User -->
   <inherits name="com.google.gwt.i18n.I18N"/>
diff --git a/user/javadoc/com/google/gwt/examples/i18n/NumberFormatConstantsAnnot.java b/user/javadoc/com/google/gwt/examples/i18n/NumberFormatConstantsAnnot.java
new file mode 100644
index 0000000..074705b
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/i18n/NumberFormatConstantsAnnot.java
@@ -0,0 +1,18 @@
+// Copyright 2006 Google Inc.  All Rights Reserved.
+package com.google.gwt.examples.i18n;
+
+import com.google.gwt.i18n.client.Constants;
+
+public interface NumberFormatConstantsAnnot extends Constants {
+  /**
+   * @return the localized decimal separator
+   */
+  @DefaultStringValue(".")
+  String decimalSeparator();
+
+  /**
+   * @return the localized thousands separator
+   */
+  @DefaultStringValue(",")
+  String thousandsSeparator();
+}
diff --git a/user/javadoc/com/google/gwt/examples/i18n/NumberFormatConstantsWithAltKey.java b/user/javadoc/com/google/gwt/examples/i18n/NumberFormatConstantsWithAltKey.java
index b2bf683..770b894 100644
--- a/user/javadoc/com/google/gwt/examples/i18n/NumberFormatConstantsWithAltKey.java
+++ b/user/javadoc/com/google/gwt/examples/i18n/NumberFormatConstantsWithAltKey.java
@@ -5,14 +5,14 @@
 
 public interface NumberFormatConstantsWithAltKey extends Constants {
   /**
-   * @gwt.key fmt.sep.decimal
    * @return the localized decimal separator
    */
+  @Key("fmt.sep.decimal")
   String decimalSeparator();
 
   /**
-   * @gwt.key fmt.sep.thousands
    * @return the localized thousands separator
    */
+  @Key("fmt.sep.thousands")
   String thousandsSeparator();
 }
diff --git a/user/src/com/google/gwt/i18n/client/Constants.java b/user/src/com/google/gwt/i18n/client/Constants.java
index deefe47..c5be987 100644
--- a/user/src/com/google/gwt/i18n/client/Constants.java
+++ b/user/src/com/google/gwt/i18n/client/Constants.java
@@ -15,6 +15,12 @@
  */
 package com.google.gwt.i18n.client;
 
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
 /**
  * A tag interface that facilitates locale-sensitive, compile-time binding of
  * constant values supplied from properties files. Using
@@ -31,8 +37,12 @@
  * <h3>Extending <code>Constants</code></h3>
  * To use <code>Constants</code>, begin by defining an interface that extends
  * it. Each interface method is referred to as a <i>constant accessor</i>, and
- * the name of each constant accessor is assumed to match the name of a property
- * defined in a properties file. For example,
+ * its corresponding localized value is loaded based on the key for that method.
+ * The default key is simply the unqualified name of the method, but can be specified
+ * directly with an {@code @Key} annotation or a different generation method using
+ * {@code @GenerateKeys}.  Also, the default value can be specified in an annotation
+ * rather than a default properties file (and some key generators may require the value
+ * to be given in the source file via annotations). For example,
  * 
  * {@example com.google.gwt.examples.i18n.NumberFormatConstants}
  * 
@@ -50,8 +60,14 @@
  * </p>
  * 
  * <p>
+ * Here is the same example using annotations to store the default values:
+ * 
+ * {@example com.google.gwt.examples.i18n.NumberFormatConstantsAnnot}
+ * </p>
+ *
+ * <p>
  * It is also possible to change the property name bound to a constant accessor
- * using the <code>gwt.key</code> doc comment. For example,
+ * using the {@code @Key} annotation. For example,
  * {@example com.google.gwt.examples.i18n.NumberFormatConstantsWithAltKey}
  * 
  * would match the names of the following properties:
@@ -70,38 +86,45 @@
  * <tr>
  * <th><nobr>If the return type is...&#160;&#160;&#160;</nobr></th>
  * <th>The property value is interpreted as...</th>
+ * <th>Annotation to use for default value</th>
  * </tr>
  * 
  * <tr>
  * <td><code>String</code></td>
  * <td>A plain string value</td>
+ * <td>{@code @DefaultStringValue}</td>
  * </tr>
  * 
  * <tr>
  * <td><code>String[]</code></td>
  * <td>A comma-separated array of strings; use '<code>\\,</code>' to escape
  * commas</td>
+ * <td>{@code @DefaultStringArrayValue}</td>
  * </tr>
  * 
  * <tr>
  * <td><code>int</code></td>
  * <td>An <code>int</code> value, checked during compilation</td>
+ * <td>{@code @DefaultIntValue}</td>
  * </tr>
  * 
  * <tr>
  * <td><code>float</code></td>
  * <td>A <code>float</code> value, checked during compilation</td>
+ * <td>{@code @DefaultFloatValue}</td>
  * </tr>
  * 
  * <tr>
  * <td><code>double</code></td>
  * <td>A <code>double</code> value, checked during compilation</td>
+ * <td>{@code @DefaultDoubleValue}</td>
  * </tr>
  * 
  * <tr>
  * <td><code>boolean</code></td>
  * <td>A <code>boolean</code> value ("true" or "false"), checked during
  * compilation</td>
+ * <td>{@code @DefaultBooleanValue}</td>
  * </tr>
  * 
  * <tr>
@@ -109,11 +132,14 @@
  * <td>A comma-separated list of property names, each of which is a key into a
  * generated map; the value mapped to given key is the value of the property
  * having named by that key</td>
+ * <td>{@code @DefaultStringMapValue}</td>
  * </tr>
  * 
  * </table>
  * 
+ * <p>
  * As an example of a <code>Map</code>, for the following property file:
+ * </p>
  * 
  * <pre>
  * a = X
@@ -122,10 +148,19 @@
  * someMap = a, b, c
  * </pre>
  * 
+ * <p>
  * the constant accessor <code>someMap()</code> would return a
  * <code>Map</code> that maps <code>"a"</code> onto <code>"X"</code>,
  * <code>"b"</code> onto <code>"Y"</code>, and <code>"c"</code> onto
  * <code>"Z"</code>.
+ * </p>
+ * 
+ * <p>The benefit of using annotations, aside from not having to switch to
+ * a different file to enter the default values, is that you can make use
+ * of compile-time constants and not worrying about quoting commas.  For example:
+ * 
+ * {@example com.google.gwt.examples.i18n.AnnotConstants}
+ * </p>
  * 
  * <h3>Binding to Properties Files</h3>
  * If an interface <code>org.example.foo.Intf</code> extends
@@ -172,7 +207,9 @@
  * 
  * </table> where <code>x</code> and <code>Y</code> are language and locale
  * codes, as described in the documentation for
- * {@link com.google.gwt.i18n.client.Localizable}.
+ * {@link com.google.gwt.i18n.client.Localizable}.  Note that default values
+ * supplied in the source file in annotations take precedence over those in
+ * the default properties file, if it is also present.
  * 
  * <p>
  * Note that the matching algorithm is applied independently for each constant
@@ -194,5 +231,101 @@
  * it since an implementation is generated automatically when message interfaces
  * are created using {@link com.google.gwt.core.client.GWT#create(Class)}.
  */
-public interface Constants extends Localizable {
+public interface Constants extends LocalizableResource {
+  /**
+   * Default boolean value to be used if no translation is found (and also used as the
+   * source for translation).  No quoting (other than normal Java string quoting)
+   * is done.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultBooleanValue {
+    boolean value();
+  }
+
+  /**
+   * Default double value to be used if no translation is found (and also used as the
+   * source for translation).  No quoting (other than normal Java string quoting)
+   * is done.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultDoubleValue {
+    double value();
+  }
+
+  /**
+   * Default float value to be used if no translation is found (and also used as the
+   * source for translation).  No quoting (other than normal Java string quoting)
+   * is done.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultFloatValue {
+    float value();
+  }
+
+  /**
+   * Default integer value to be used if no translation is found (and also used as the
+   * source for translation).  No quoting (other than normal Java string quoting)
+   * is done.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultIntValue {
+    int value();
+  }
+
+  /**
+   * Default string array value to be used if no translation is found (and also
+   * used as the source for translation). No quoting (other than normal Java
+   * string quoting) is done.
+   * 
+   * Note that in the corresponding properties/etc file, commas are used to separate
+   * elements of the array unless they are preceded with a backslash.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultStringArrayValue {
+    String[] value();
+  }
+
+  /**
+   * Default string map value to be used if no translation is found (and also
+   * used as the source for translation). No quoting (other than normal Java
+   * string quoting) is done.  The strings for the map are supplied in key/value
+   * pairs.
+   * 
+   * Note that in the corresponding properties/etc file, new keys can be supplied
+   * with the name of the method (or its corresponding key) listing the set of keys
+   * for the map separated by commas (commas can be part of the keys by preceding
+   * them with a backslash).  In either case, further entries have keys matching
+   * the key in this map.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultStringMapValue {
+    /**
+     * Must be key-value pairs.
+     */
+    String[] value();
+  }
+
+  /**
+   * Default string value to be used if no translation is found (and also used as the
+   * source for translation).  No quoting (other than normal Java string quoting)
+   * is done.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultStringValue {
+    String value();
+  }
 }
diff --git a/user/src/com/google/gwt/i18n/client/LocaleInfo.java b/user/src/com/google/gwt/i18n/client/LocaleInfo.java
index 4ebdcaa..4d450d4 100644
--- a/user/src/com/google/gwt/i18n/client/LocaleInfo.java
+++ b/user/src/com/google/gwt/i18n/client/LocaleInfo.java
@@ -20,7 +20,7 @@
 import com.google.gwt.i18n.client.impl.LocaleInfoImpl;
 
 /**
- * Provides access to the currently-running locale and the list of available
+ * Provides access to the currently-active locale and the list of available
  * locales.
  */
 public class LocaleInfo {
@@ -35,7 +35,7 @@
       (CldrImpl) GWT.create(CldrImpl.class));
 
   /**
-   * @return an array of available locales
+   * @return an array of available locale names
    */
   public static String[] getAvailableLocaleNames() {
     /*
@@ -53,16 +53,15 @@
   }
   
   /**
-   * @return a LocaleInfo instance for the current locale.
-   * 
-   * In the future, we could make additional static methods which returned
-   * a LocaleInfo instance for a specific locale (from the set of those
-   * the app was compiled with), accessed via a method like:
-   * <pre>
-   *   public static LocaleInfo getLocale(String localeName)
-   * </pre>
+   * @return a LocaleInfo instance for the current locale
    */
   public static LocaleInfo getCurrentLocale() {
+    /*
+     * In the future, we could make additional static methods which returned a
+     * LocaleInfo instance for a specific locale (from the set of those the app
+     * was compiled with), accessed via a method like:
+     *    public static LocaleInfo getLocale(String localeName)
+     */
     return instance;
   }
 
@@ -110,14 +109,14 @@
   }
 
   /**
-   * @return the name of this locale, such as "default, "en_US", etc.
+   * @return the name of this locale, such as "default, "en_US", etc
    */
   public String getLocaleName() {
     return infoImpl.getLocaleName();
   }
   
   /**
-   * @return true if this locale is right-to-left instead of left-to-right.
+   * @return true if this locale is right-to-left instead of left-to-right
    */
   public boolean isRTL() {
     return cldrImpl.isRTL();
diff --git a/user/src/com/google/gwt/i18n/client/LocalizableResource.java b/user/src/com/google/gwt/i18n/client/LocalizableResource.java
new file mode 100644
index 0000000..3f111b1
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/LocalizableResource.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2008 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.client;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * This is the common superinterface to Messages and Constants.
+ * 
+ * Each (and the Constants subinterface ConstantsWithLookup) provide
+ * compile-time localization of various forms of data.  Messages is
+ * used for <code>MessageFormat</code>-style strings which can have
+ * parameters (including support for plural forms), while Constants
+ * can be other types, have simplified quoting requirements, and do
+ * not take any parameters.
+ * 
+ * The annotations defined here are common to both -- see the individual
+ * subinterfaces for additional annotations which apply only to each
+ * one.
+ */
+public interface LocalizableResource extends Localizable {
+
+  /**
+   * Specifies the default locale for messages in this file.  If not
+   * specified, the default is en_US.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface DefaultLocale {
+    String value() default "en_US";
+  }
+
+  /**
+   * Specifies a description of the string to be translated, such as a note
+   * about the context.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface Description {
+    String value();
+  }
+
+  /**
+   * Requests that a translation source file be generated from the annotated
+   * interface.  The file type is determined by the format argument, and the
+   * file name by the optional fileName argument.  Some file formats support
+   * aggregating messages from multiple interfaces into one file, while others
+   * do not; also, additional parameters may be specified via format-specific
+   * annotations -- see the documentation of the MessageCatalogFormat implementation
+   * for details.
+   * 
+   * Examples:
+   * <ul>
+   * <li>{@code @Generate(format = "com.google.gwt.i18n.rebind.format.Properties")}
+   * <br>generates properties files for all locales, and the names will be
+   *      of the form MyMessages_locale.properties
+   * <li>{@code @Generate(format = {"com.example.ProprietaryFormat1",
+   *    "com.example.ProprietaryFormat2"},
+   *    fileName = "myapp_translate_source", locales = {"default"})}
+   * <br>generates default files in two proprietary formats, with filenames like
+   *      myapp_translate_source.p1 and myapp_translate_source.p2
+   * </pre></code>  
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface Generate {
+
+    /**
+     * Constant "magic" default value used to detect that no value was supplied
+     * for the fileName parameter.
+     */
+    String DEFAULT = "[default]";
+    
+    /**
+     * Fully-qualified class names of the generator classes. Each class must implement
+     * com.google.gwt.i18n.rebind.format.MessageCatalogFormat.
+     * 
+     * Strings are used here instead of class literals because the generators will
+     * likely contain non-translatable code and thus can't be referenced from
+     * translatable code directly.
+     * 
+     * Each generator may define additional annotations to supply other necessary
+     * parameters.
+     */
+    String[] format();
+
+    /**
+     * A platform-specific filename for output. If not present, the file will be
+     * named based on the fully-qualified name of the annotated interface. File
+     * names without a slash are given a relative name based on the
+     * fully-qualified package name of the annotated interface. Relative pathnames
+     * are generated in the directory specified by the "-out" flag to the
+     * compiler, or the current directory if not present.  Unless exactly one locale
+     * is specified for locales (not just only one locale happened to be compiled for),
+     * the locale will be appended to the name (such as _default [for the default
+     * locale], _en_US, etc) as well as the proper extension for the specified format.
+     * 
+     * Note that if multiple generators are used, they will have the same base
+     * filename so the extensions must be different.
+     */
+    String fileName() default DEFAULT;
+    
+    /**
+     * A list of locales for which to generate this output file.  If no locales
+     * are specified, all locales for which the application is compiled for will
+     * be generated. Note that the default locale is "default".
+     */
+    String[] locales() default {};
+  }
+
+  /**
+   * Annotation indicating this is a generated file and the source file it was
+   * generated from. 
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface GeneratedFrom {
+    String value();
+  }
+
+  /**
+   * Requests that the keys for messages be generated automatically.  The
+   * default value is to use an MD5 hash of the text and meaning.  If this
+   * annotation is not supplied, the keys will be the unqualified method
+   * names.
+   * 
+   * The value is either the name of an inner class of {@code KeyGenerator} or the
+   * fully-qualified class name of some implementation of {@code KeyGenerator}.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.TYPE)
+  public @interface GenerateKeys {
+    String value() default "com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator";
+  }
+
+  /**
+   * The key used for lookup of translated strings.  If not present, the
+   * key will be generated based on the {@code @GenerateKeysUsing} annotation,
+   * or the unqualified method name if it is not present.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface Key {
+    String value();
+  }
+
+  /**
+   * Specifies the meaning of the translated string.  For example, to
+   * distinguish between multiple meanings of a word or phrase.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface Meaning {
+    String value();
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/i18n/client/Messages.java b/user/src/com/google/gwt/i18n/client/Messages.java
index b0f72a7..f2d0fae 100644
--- a/user/src/com/google/gwt/i18n/client/Messages.java
+++ b/user/src/com/google/gwt/i18n/client/Messages.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -16,15 +16,21 @@
 
 package com.google.gwt.i18n.client;
 
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
 /**
  * A tag interface that facilitates locale-sensitive, compile-time binding of
- * messages supplied from properties files. Using
+ * messages supplied from various sources.Using
  * <code>GWT.create(<i>class</i>)</code> to "instantiate" an interface that
  * extends <code>Messages</code> returns an instance of an automatically
- * generated subclass that is implemented using message templates from a
- * property file selected based on locale. Message templates are based on a
- * subset of the format used by <a
- * href="http://java.sun.com/j2se/1.5.0/docs/api/java/text/MessageFormat.html"><code>MessageFormat</code></a>.
+ * generated subclass that is implemented using message templates selected based
+ * on locale. Message templates are based on a subset of the format used by <a
+ * href="http://java.sun.com/j2se/1.5.0/docs/api/java/text/MessageFormat.html">
+ * <code>MessageFormat</code></a>.
  * 
  * <p>
  * Locale is specified at run time using a meta tag or query string as described
@@ -34,8 +40,15 @@
  * <h3>Extending <code>Messages</code></h3>
  * To use <code>Messages</code>, begin by defining an interface that extends
  * it. Each interface method is referred to as a <i>message accessor</i>, and
- * the name of each message accessor is assumed to match the name of a property
- * defined in a properties file. For example,
+ * its corresponding message template is loaded based on the key for that
+ * method. The default key is simply the unqualified name of the method, but can
+ * be specified directly with an {@code @Key} annotation or a different
+ * generation method using {@code @GenerateKeys}. Additionally, if
+ * plural forms are used on a given method the plural form is added as a suffix
+ * to the key, such as <code>widgets[one]</code> for the singular version of
+ * the <code>widgets</code> message. The resulting key is used to find
+ * translated versions of the message from any supported input file, such as
+ * Java properties files. For example,
  * 
  * {@example com.google.gwt.examples.i18n.GameStatusMessages}
  * 
@@ -54,10 +67,11 @@
  * {@example com.google.gwt.examples.i18n.GameStatusMessagesExample#beginNewGameRound(String)}
  * </p>
  * 
- * <p>
- * It is possible to change the property name bound to a message accessor using
- * the <code>gwt.key</code> doc comment, exactly as is done for constant
- * accessors. See {@link Constants} for an example.
+ * <p>The following example shows how to use annotations to store the default strings
+ * in the source file itself, rather than needing a properties file (you still need
+ * properties files for the translated strings):
+ * 
+ * {@example com.google.gwt.examples.i18n.GameStatusMessagesAnnot}
  * </p>
  * 
  * <h3>Defining Message Accessors</h3>
@@ -76,8 +90,65 @@
  * parameters supplied.
  * </p>
  * 
+ * <p>
+ * Integral arguments may be used to select the proper plural form to use for
+ * different locales. To do this, mark the particular argument with
+ * {@code @PluralCount} (a plural rule may be specified with
+ * {@code @PluralCount} if necessary, but you will almost never need to
+ * do this). The actual plural forms for the default locale can be supplied in a
+ * {@code @PluralText} annotation on the method, such as
+ * {@code @PluralText({"one", "You have one widget"})}, or they can be
+ * supplied in the properties file as {@code methodkey[one]=You have one widget}. Note
+ * that non-default plural forms are not inherited between locales, because the
+ * different locales may have different plural rules (especially {@code default} and
+ * anything else and those which use different scripts such as {@code sr_Cyrl} and
+ * {@code sr_Latn} [one of which would likely be the default], but also subtle cases
+ * like {@code pt} and {@code pt_BR}).
+ * </p>
+ * 
+ * <p>
+ * Additionally, individual arguments can be marked as optional (ie, GWT will
+ * not give an error if a particular translation does not reference the
+ * argument) with the {@code @Optional} annotation, and an example may be supplied to
+ * the translator with the {@code @Example(String)} annotation.
+ * </p>
+ * 
+ * <h3>Complete Annotations Example</h3>
+ * In addition to the default properties file, default text and additional
+ * metadata may be stored in the source file itself using annotations. A
+ * complete example of using annotations in this way is:
+ * 
+ * <code><pre>
+ * &#64;Generate(format = "com.google.gwt.i18n.rebind.format.PropertiesFormat")
+ * &#64;DefaultLocale("en_US")
+ * public interface MyMessages extends Messages {
+ *   &#64;Key("1234")
+ *   &#64;DefaultText("This is a plain string.")
+ *   String oneTwoThreeFour();
+ *   
+ *   &#64;DefaultText("You have {0} widgets")
+ *   &#64;PluralText({"one", "You have one widget")
+ *   String widgetCount(&#64;PluralCount int count);
+ *   
+ *   &#64;DefaultText("No reference to the argument")
+ *   String optionalArg(&#64;Optional String ignored);
+ *   
+ *   &#64;DefaultText("Your cart total is {0,number,currency}")
+ *   &#64;Description("The total value of the items in the shopping cart in local currency")
+ *   String totalAmount(&#64;Example("$5.00") double amount);
+ *   
+ *   &#64;Meaning("the color")
+ *   &#64;DefaultMessage("orange")
+ *   String orangeColor();
+ *   
+ *   &#64;Meaning("the fruit")
+ *   &#64;DefaultMessage("orange")
+ *   String orangeFruit();
+ * }
+ * </pre></code>
+ * 
  * <h3>Binding to Properties Files</h3>
- * Interfaces extending <code>Messages</code> are bound to properties file
+ * Interfaces extending <code>Messages</code> are bound to resource files
  * using the same algorithm as interfaces extending <code>Constants</code>.
  * See the documentation for {@link Constants} for a description of the
  * algorithm.
@@ -93,5 +164,125 @@
  * it since an implementation is generated automatically when message interfaces
  * are created using {@link com.google.gwt.core.client.GWT#create(Class)}.
  */
-public interface Messages extends Localizable {
+public interface Messages extends LocalizableResource {
+
+  /**
+   * Default text to be used if no translation is found (and also used as the
+   * source for translation). Format should be that expected by
+   * {@link MessageFormat}.
+   * 
+   * <p>Example:
+   * <code><pre>
+   *   &#64;DefaultMessage("Don''t panic - you have {0} widgets left")
+   *   String example(int count)
+   * </pre></code>
+   * </p>
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface DefaultMessage {
+    String value();
+  }
+
+  /**
+   * An example of the annotated parameter to assist translators.
+   * 
+   * <p>Example:
+   * <code><pre>
+   *   String example(&#64;Example("/etc/passwd") String fileName)
+   * </pre></code>
+   * </p>
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.PARAMETER)
+  public @interface Example {
+    String value();
+  }
+
+  /**
+   * Indicates the specified parameter is optional and need not appear in a
+   * particular translation of this message.
+   * 
+   * <p>Example:
+   * <code><pre>
+   *   String example(&#64;Optional int count)
+   * </pre></code>
+   * </p>
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.PARAMETER)
+  public @interface Optional {
+  }
+
+  /**
+   * Provides multiple plural forms based on a count. The selection of which
+   * plural form is performed by a PluralRule implementation.
+   * 
+   * This annotation is applied to a single parameter of a Messages subinterface
+   * and indicates that parameter is to be used to choose the proper plural form
+   * of the message. The parameter chosen must be of type short or int.
+   * 
+   * Optionally, a class literal referring to a PluralRule implementation can be
+   * supplied as the argument if the standard implementation is insufficient.
+   * 
+   * <p>Example:
+   * <code><pre>
+   *   &#64;DefaultMessage("You have {0} widgets.")
+   *   &#64;PluralText({"one", "You have one widget.")
+   *   String example(&#64;PluralCount int count)
+   * </pre></code>
+   * </p>
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.PARAMETER)
+  public @interface PluralCount {
+
+    /**
+     * The PluralRule implementation to use for this message. If not specified,
+     * the GWT-supplied one is used instead, which should cover most use cases.
+     * 
+     * <p>{@code PluralRule.class} is used as a default value here, which will
+     * be replaced during code generation with the default implementation.
+     * </p>
+     */
+    Class<? extends PluralRule> value() default PluralRule.class;
+  }
+
+  /**
+   * Provides multiple plural forms based on a count. The selection of which
+   * plural form to use is based on the value of the argument marked
+   * PluralCount, which may also specify an alternate plural rule to use.
+   * 
+   * <p>Example:
+   * <code><pre>
+   *   &#64;DefaultMessage("You have {0} widgets.")
+   *   &#64;PluralText({"one", "You have one widget.")
+   *   String example(&#64;PluralCount int count)
+   * </pre></code>
+   * </p>
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  @Documented
+  public @interface PluralText {
+
+    /**
+     * An array of pairs of strings containing the strings for different plural
+     * forms.
+     * 
+     * Each pair is the name of the plural form (as returned by
+     * PluralForm.toString) followed by the string for that plural form. An
+     * example for a locale that has "none", "one", and "other" plural forms:
+     * 
+     * <code><pre>
+     * &#64;DefaultMessage("{0} widgets")
+     * &#64;PluralText({"none", "No widgets", "one", "One widget"})
+     * </pre>
+     * 
+     * "other" must not be included in this array as it will map to the
+     * DefaultMessage value.
+     */
+    String[] value();
+  }
 }
diff --git a/user/src/com/google/gwt/i18n/client/PluralRule.java b/user/src/com/google/gwt/i18n/client/PluralRule.java
new file mode 100644
index 0000000..23c6949
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/PluralRule.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2008 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.client;
+
+/**
+ * The interface that plural rules must implement.  Implementations of
+ * this interface will be used both at compile time (pluralForms) and
+ * at run time (select), so implementations must be both translatable
+ * and not reference JSNI methods.
+ */
+public interface PluralRule {
+
+  /**
+   * Information about the plural forms supported by this rule which
+   * will be used during code generation and by tools to provide
+   * information to translators.
+   */
+  public static class PluralForm {
+    private final String name;
+    private final String description;
+    private final boolean noWarn;
+
+    /**
+     * Create the plural form.
+     * 
+     * @param name
+     * @param description
+     */
+    public PluralForm(String name, String description) {
+      this(name, description, false);
+    }
+    
+    /**
+     * Create the plural form.
+     * 
+     * @param name
+     * @param description
+     * @param noWarn if true, do not warn if this form is missing from a
+     *     translation.  This is used for those cases where a plural form
+     *     is defined for a language, but is very rarely used.
+     */
+    public PluralForm(String name, String description, boolean noWarn) {
+      this.name = name;
+      this.description = description;
+      this.noWarn = noWarn;
+    }
+
+    /**
+     * @return the description.
+     */
+    public String getDescription() {
+      return description;
+    }
+    
+    /**
+     * @return the name.
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * @return true if the generator should warn if this plural form
+     *     is not present.
+     */
+    public boolean getWarnIfMissing() {
+      return !noWarn;
+    }
+  }
+  
+  /**
+   * Returns the list of values which are valid for this rule.  The
+   * default or "other" plural form must be first in the list with
+   * an index of 0 -- this form will be used if no other form applies
+   * and is also mapped to the default text for a given message.
+   * 
+   * This method will be executed at compile time and may not contain
+   * any references, even indirectly, to JSNI methods.
+   */
+  PluralForm[] pluralForms();
+  
+  /**
+   * Returns the plural form appropriate for this count.
+   * 
+   * This method will be executed at runtime, so must be translatable.
+   * 
+   * @param n count of items to choose plural form for
+   * @return the plural form to use (must be a valid index
+   *     into the array returned by pluralForms).
+   */
+  int select(int n);
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ar.java b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ar.java
similarity index 82%
rename from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ar.java
rename to user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ar.java
index ef36dd6..26cccf3 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ar.java
+++ b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ar.java
@@ -13,15 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
+package com.google.gwt.i18n.client.impl.cldr;
 
 import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
  * Placeholder for generated file.
+ * 
+ * Eventually all CldrImpl implementations will be machine-generated from
+ * Unicode's CLDR data.
  */
 public class CldrImpl_ar extends CldrImpl {
 
+  @Override
   public boolean isRTL() {
     return true;
   }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_fa.java b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_fa.java
similarity index 82%
rename from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_fa.java
rename to user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_fa.java
index a370fb6..78b7399 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_fa.java
+++ b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_fa.java
@@ -13,15 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
+package com.google.gwt.i18n.client.impl.cldr;
 
 import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
  * Placeholder for generated file.
+ * 
+ * Eventually all CldrImpl implementations will be machine-generated from
+ * Unicode's CLDR data.
  */
 public class CldrImpl_fa extends CldrImpl {
 
+  @Override
   public boolean isRTL() {
     return true;
   }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_he.java
similarity index 82%
rename from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
rename to user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_he.java
index eaca740..b0e9c60 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_he.java
@@ -13,15 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
+package com.google.gwt.i18n.client.impl.cldr;
 
 import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
  * Placeholder for generated file.
+ * 
+ * Eventually all CldrImpl implementations will be machine-generated from
+ * Unicode's CLDR data.
  */
 public class CldrImpl_he extends CldrImpl {
 
+  @Override
   public boolean isRTL() {
     return true;
   }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ps.java b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ps.java
similarity index 82%
rename from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ps.java
rename to user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ps.java
index 3c2953d..ea059c5 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ps.java
+++ b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ps.java
@@ -13,15 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
+package com.google.gwt.i18n.client.impl.cldr;
 
 import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
  * Placeholder for generated file.
+ * 
+ * Eventually all CldrImpl implementations will be machine-generated from
+ * Unicode's CLDR data.
  */
 public class CldrImpl_ps extends CldrImpl {
 
+  @Override
   public boolean isRTL() {
     return true;
   }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ur.java b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ur.java
similarity index 82%
rename from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ur.java
rename to user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ur.java
index e050454..f598f72 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_ur.java
+++ b/user/src/com/google/gwt/i18n/client/impl/cldr/CldrImpl_ur.java
@@ -13,15 +13,19 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
+package com.google.gwt.i18n.client.impl.cldr;
 
 import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
  * Placeholder for generated file.
+ * 
+ * Eventually all CldrImpl implementations will be machine-generated from
+ * Unicode's CLDR data.
  */
 public class CldrImpl_ur extends CldrImpl {
 
+  @Override
   public boolean isRTL() {
     return true;
   }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-generated.properties b/user/src/com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-generated.properties
similarity index 100%
rename from user/src/com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-generated.properties
rename to user/src/com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-generated.properties
diff --git a/user/src/com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-manual.properties b/user/src/com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-manual.properties
similarity index 100%
rename from user/src/com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-manual.properties
rename to user/src/com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-manual.properties
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/cldr/package-info.java
similarity index 70%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/cldr/package-info.java
index eaca740..6e4a7b9 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/cldr/package-info.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
-
-import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
- * Placeholder for generated file.
+ * This package contains classes which are machine-generated from Unicode
+ * CLDR data -- see http://www.unicode.com/cldr/
+ *
+ * The classes currently here were hand-written based on the CLDR data.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
-}
+package com.google.gwt.i18n.client.impl.cldr;
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule.java
new file mode 100644
index 0000000..1186e89
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.Localizable;
+import com.google.gwt.i18n.client.PluralRule;
+
+/**
+ * Default implementation of plural rules.  The i18n generator will
+ * substitute subclasses of this class, which may reside anywhere in
+ * the package hierarchy, based on the locale.  See Localizable
+ * for more details on this process.
+ * 
+ * The default implementation here always selects the default form.
+ * 
+ * To define a new language, simply declare a subclass of this class
+ * that is named with the locale you want to specify, such as
+ * DefaultRule_en_uk for English spoken in the UK (note the lower-cased
+ * tag).
+ * 
+ * Many of the rules implemented in subclasses of this class are from:
+ *   http://translate.sourceforge.net/wiki/l10n/pluralforms
+ * 
+ * Eventually, all these rules will be machine generated from Unicode's
+ * CLDR, perhaps with some additional data that isn't kept there if
+ * necessary.  The current subclasses are defined just to get reasonable
+ * plural support for most of the common languages -- in particular, you
+ * should not rely on particular keywords for the plural forms of a
+ * given language.
+ */
+public class DefaultRule implements Localizable, PluralRule {
+
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+    };
+  }
+
+  public int select(int n) {
+    return 0;
+  }
+
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_01_n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_01_n.java
new file mode 100644
index 0000000..ec46aa7
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_01_n.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Common plural rule for languages that have singular and plural forms,
+ * treating 0 as singular.
+ * 
+ * @see DefaultRule_0_1_2_n
+ * @see DefaultRule_0_1_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_2_n
+ * @see DefaultRule_1_234_n
+ * @see DefaultRule_1_paucal_n
+ * @see DefaultRule_x1_x234_n
+ */
+public class DefaultRule_01_n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 0 or 1"),
+    };
+  }
+
+  public static int select(int n) {
+    return n == 0 || n == 1 ? 1 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_0_1_2_n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_0_1_2_n.java
new file mode 100644
index 0000000..5647fe8
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_0_1_2_n.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Common plural rule for languages that have 0, 1, 2, and other forms.
+ * 
+ * @see DefaultRule_0_1_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_2_n
+ * @see DefaultRule_1_234_n
+ * @see DefaultRule_1_paucal_n
+ * @see DefaultRule_x1_x234_n
+ */
+public class DefaultRule_0_1_2_n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("none", "Count is 0"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("two", "Count is 2"),
+    };
+  }
+
+  public static int select(int n) {
+    return n >= 0 && n <= 2 ? n + 1 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_0_1_n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_0_1_n.java
new file mode 100644
index 0000000..61ba798
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_0_1_n.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Common plural rule for languages that have none, singular and plural forms.
+ * 
+ * @see DefaultRule_0_1_2_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_2_n
+ * @see DefaultRule_1_234_n
+ * @see DefaultRule_1_paucal_n
+ * @see DefaultRule_x1_x234_n
+ */
+public class DefaultRule_0_1_n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("none", "Count is 0"),
+        new PluralForm("one", "Count is 1"),
+    };
+  }
+
+  public static int select(int n) {
+    return n == 0 ? 1 : n == 1 ? 2 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_0n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_0n.java
new file mode 100644
index 0000000..0877e6f
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_0n.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Plural rule for languages that have a singular form and treat 0 as the plural.
+ * 
+ * @see DefaultRule_0_1_2_n
+ * @see DefaultRule_0_1_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_2_n
+ * @see DefaultRule_1_234_n
+ * @see DefaultRule_1_paucal_n
+ * @see DefaultRule_x1_x234_n
+ */
+public class DefaultRule_1_0n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+    };
+  }
+
+  /**
+   * Returns the list of plural forms, indicating the singular form is optional.
+   */
+  public static PluralForm[] pluralFormsOptional() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1", true),
+    };
+  }
+
+  public static int select(int n) {
+    return n == 1 ? 1 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_234_n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_234_n.java
new file mode 100644
index 0000000..077d966
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_234_n.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Common plural rule for languages that have singular, 2-4, and other plural
+ * forms. Some Slavic languages use this form.
+ * 
+ * @see DefaultRule_0_1_2_n
+ * @see DefaultRule_0_1_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_2_n
+ * @see DefaultRule_1_paucal_n
+ * @see DefaultRule_x1_x234_n for the form used by other Slavic languages
+ */
+public class DefaultRule_1_234_n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("few", "Count is 2, 3 or 4"),
+    };
+  }
+
+  public static int select(int n) {
+    return n == 1 ? 1 : n >= 2 && n  <= 4 ? 2 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_2_n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_2_n.java
new file mode 100644
index 0000000..d788e4c
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_2_n.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Common plural rule for languages that have singular, dual, and plural forms.
+ * 
+ * @see DefaultRule_0_1_2_n
+ * @see DefaultRule_0_1_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_234_n
+ * @see DefaultRule_1_paucal_n
+ * @see DefaultRule_x1_x234_n
+ */
+public class DefaultRule_1_2_n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("two", "Count is 2"),
+    };
+  }
+
+  public static int select(int n) {
+    return n == 1 ? 1 : n == 2 ? 2 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_paucal_n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_paucal_n.java
new file mode 100644
index 0000000..7f43656
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_1_paucal_n.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Common plural rule for languages that have 1, few, and other forms.
+ * 
+ * @see DefaultRule_0_1_2_n
+ * @see DefaultRule_0_1_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_2_n
+ * @see DefaultRule_1_234_n
+ * @see DefaultRule_x1_x234_n
+ */
+public class DefaultRule_1_paucal_n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("paucal", "Count ends in 2-4 but not 12-14 or 22-24"),
+    };
+  }
+
+  public static int select(int n) {
+    /*
+     * For Polish, numbers that end in 2-4, except 12-14 and 22-24, have a special plural form.
+     */
+    return n == 1 ? 1
+        : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 > 29) ? 2
+        : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_af.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_af.java
index eaca740..f56d993 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_af.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Afrikaans are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_af extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_am.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_am.java
index eaca740..dfa68fc 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_am.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Amharic are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_am extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ar.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ar.java
new file mode 100644
index 0000000..b58c722
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ar.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Arabic are 0, 1, 2, 3-10, 11-99, and n.
+ */
+public class DefaultRule_ar extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("none", "Count is 0"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("two", "Count is 2"),
+        new PluralForm("few", "Count is between 3 and 10"),
+        new PluralForm("many", "Count is between 11 and 99"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n == 0 ? 1 : n == 1 ? 2 : n == 2 ? 3
+        : n >= 3 && n <= 10 ? 4 : n >= 11 && n <= 99 ? 5 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_az.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_az.java
index eaca740..9c93def 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_az.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Azerbaijani are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_az extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_be.java
similarity index 61%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_be.java
index eaca740..c4a979e 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_be.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Belarusian are x1 (but not x11), x2-x4 (but not x12-x14), and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_be extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_x1_x234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_x1_x234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bg.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bg.java
index eaca740..855e374 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bg.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Bulgarian are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_bg extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bh.java
similarity index 62%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bh.java
index eaca740..8403233 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bh.java
@@ -13,16 +13,24 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Bhojpuri are 1 and n, with 0 treated as singular.
+ * 
+ * TODO: verify
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_bh extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bn.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bn.java
index eaca740..2fdc005 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bn.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Bengali are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_bn extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bo.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bo.java
index eaca740..4101c16 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bo.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plurals in Tibetan, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_bo extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bs.java
similarity index 61%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bs.java
index eaca740..555a05e 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_bs.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Bosnian are x1 (but not x11), x2-x4 (but not x12-x14), and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_bs extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_x1_x234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_x1_x234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ca.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ca.java
index eaca740..d55a168 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ca.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Catalan are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ca extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_cs.java
similarity index 64%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_cs.java
index eaca740..5aa5cae 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_cs.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Czech are 1, 2-4, and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_cs extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_cy.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_cy.java
new file mode 100644
index 0000000..9ac5062
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_cy.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Welsh are 1, 2, 8/11, and n.
+ */
+public class DefaultRule_cy extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("two", "Count is 2"),
+        new PluralForm("eighteleven", "Count is either 8 or 11"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n == 1 ? 1 : n == 2 ? 2 : n == 8 || n == 11 ? 3 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_da.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_da.java
index eaca740..e63695d 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_da.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Danish are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_da extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_de.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_de.java
index eaca740..319421f 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_de.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for German are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_de extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_dz.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_dz.java
index eaca740..e6f24c2 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_dz.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Dzongkha, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_dz extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_el.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_el.java
index eaca740..83def21 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_el.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Modern Greek are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_el extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_en.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_en.java
index eaca740..e904bdc 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_en.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for English are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_en extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_eo.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_eo.java
index eaca740..c4e6eee 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_eo.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Esperanto are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_eo extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_es.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_es.java
index eaca740..c20f835 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_es.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Spanish are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_es extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_et.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_et.java
index eaca740..a2d9a58 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_et.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Estonian are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_et extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_eu.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_eu.java
index eaca740..90fb707 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_eu.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Basque are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_eu extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fa.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fa.java
new file mode 100644
index 0000000..3442b52
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fa.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Farsi (Persian) are 1 and n, with 0 treated as plural.
+ * 
+ * Usually special forms are not needed for singular, so the singular case is
+ * marked as optional so if it is missing no warning will be generated.
+ */
+public class DefaultRule_fa extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralFormsOptional();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fi.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fi.java
index eaca740..f5b5677 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fi.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Finnish are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_fi extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fil.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fil.java
index eaca740..c446f13 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fil.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Filipino are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_fil extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fo.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fo.java
index eaca740..8f2e583 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fo.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Faroese are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_fo extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fr.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fr.java
index eaca740..363023d 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fr.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for French are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_fr extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fur.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fur.java
index eaca740..e0a47be 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fur.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Friulian are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_fur extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fy.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fy.java
index eaca740..c7bed9f 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_fy.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Frisian are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_fy extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ga.java
similarity index 65%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ga.java
index eaca740..cb1ac40 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ga.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Irish are 1, 2, and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ga extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_2_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_2_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_gl.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_gl.java
index eaca740..7731971 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_gl.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Galician are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_gl extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_gu.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_gu.java
index eaca740..a625bf3 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_gu.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Gujarati are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_gu extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_guw.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_guw.java
index eaca740..8ddec73 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_guw.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Gun are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_guw extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ha.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ha.java
index eaca740..e1ac911 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ha.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Hausa are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ha extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_he.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_he.java
index eaca740..eba8762 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_he.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Hebrew are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_he extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hi.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hi.java
index eaca740..aa2ac8b 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hi.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Hindi are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_hi extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hr.java
similarity index 61%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hr.java
index eaca740..5f1176a 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hr.java
@@ -13,16 +13,23 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Croatian are x1 (but not x11), x2-x4 (but not x12-x14),
+ * and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_hr extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_x1_x234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_x1_x234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hu.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hu.java
new file mode 100644
index 0000000..01dad42
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_hu.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Hungarian are 1 and n, with 0 treated as plural.
+ * 
+ * In most cases, there is only one form, but in a few cases the singular case
+ * needs to be treated differently.  It is perfectly acceptable to have only
+ * the "other" plural form for most translations.
+ */
+public class DefaultRule_hu extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralFormsOptional();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_id.java
similarity index 68%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_id.java
index eaca740..b185070 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_id.java
@@ -13,16 +13,13 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Indonesian are formed by reduplication, but this is
+ * unnecessary when a numeral is used.  Therefore, we do not consider plural
+ * forms for this use case.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_id extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_is.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_is.java
index eaca740..d094e30 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_is.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Icelandic are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_is extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_it.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_it.java
index eaca740..065daa0 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_it.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Italian are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_it extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ja.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ja.java
index eaca740..d4c9a48 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ja.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Japanese, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_ja extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_jv.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_jv.java
index eaca740..ba0e934 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_jv.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Javanese, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_jv extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ka.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ka.java
index eaca740..c2ed396 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ka.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Georgian, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_ka extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_km.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_km.java
index eaca740..b0e3b0b 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_km.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Khmer, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_km extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_kn.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_kn.java
index eaca740..0042219 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_kn.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Kannada, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_kn extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ko.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ko.java
index eaca740..c43aafb 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ko.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Korean, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_ko extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ku.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ku.java
index eaca740..7aaa467 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ku.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Kurdish are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ku extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lb.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lb.java
index eaca740..9a289af 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lb.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Letzeburgesch are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_lb extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ln.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ln.java
index eaca740..25a5fc8 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ln.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Lingala are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ln extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lt.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lt.java
new file mode 100644
index 0000000..01f1b97
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lt.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Lithuanian are x1 (except x11), x2-x9 (except x12-x19), and n.
+ */
+public class DefaultRule_lt extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count ends in 1 but not 11"),
+        new PluralForm("few", "Count ends in 2-9 but not 12-19"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n % 10 == 1 && n % 100 != 11 ? 1
+        : n % 10 >= 2 && (n % 100 < 10 || n % 100 >= 20) ? 2
+        : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lv.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lv.java
new file mode 100644
index 0000000..1221e7d
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_lv.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Latvian are 0, x1 (except x11), and n.
+ */
+public class DefaultRule_lv extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("none", "Count is 0"),
+        new PluralForm("one", "Count ends in 1 but not 11"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n == 0 ? 1 : n % 10 == 1 && n % 100 != 11 ? 2 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mg.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mg.java
index eaca740..be0aac9 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mg.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Malgasy are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_mg extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mk.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mk.java
new file mode 100644
index 0000000..dabd3a6
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mk.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Macedonian are x1 and n.
+ */
+public class DefaultRule_mk extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count ends in 1"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n % 10 == 1 ? 1 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ml.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ml.java
index eaca740..93a7fa5 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ml.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Malayalam are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ml extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mn.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mn.java
new file mode 100644
index 0000000..5c04849
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mn.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Mongolian are 1 and n, with 0 treated as plural.
+ * 
+ * Plural forms are not required to be marked, but it is optional so
+ * a plural form is provided here.  No warning will be issued if it
+ * is not included, as it is acceptable to use the same form.
+ */
+public class DefaultRule_mn extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralFormsOptional();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mr.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mr.java
index eaca740..bc4c453 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mr.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Marathi are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_mr extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ms.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ms.java
index eaca740..65dd195 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ms.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plurals in Malay, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_ms extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mt.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mt.java
new file mode 100644
index 0000000..41d4bca
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_mt.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Maltese are 1, 0/x2-x10, x11-x19, and n.
+ */
+public class DefaultRule_mt extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("few", "Count is 0 or ends in 02-10"),
+        new PluralForm("many", "Count ends in 11-19"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n == 1 ? 1 : n == 0 || (n % 100 > 1 && n % 100 < 11) ? 2
+        : (n % 100 > 10 && n % 100 < 20) ? 3 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nah.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nah.java
index eaca740..f918e78 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nah.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Nahuati are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_nah extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nb.java
similarity index 62%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nb.java
index eaca740..50a1066 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nb.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Norwegian Bokmal are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_nb extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ne.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ne.java
index eaca740..5325f4e 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ne.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Nepali are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ne extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nl.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nl.java
index eaca740..a519988 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nl.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Dutch are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_nl extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nn.java
similarity index 62%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nn.java
index eaca740..7a3b64b 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nn.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Norwegian Nynorsk are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_nn extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_no.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_no.java
index eaca740..1929acc 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_no.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Norwegian are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_no extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nso.java
similarity index 62%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nso.java
index eaca740..9ca2ee4 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_nso.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Northern Sotho are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_nso extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_om.java
similarity index 60%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_om.java
index eaca740..5255384 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_om.java
@@ -13,16 +13,24 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Oromo are 1 and n, with 0 treated as plural.
+ * 
+ * TODO: verify that 0 is considered plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_om extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_or.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_or.java
index eaca740..3dfddb6 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_or.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Oriya are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_or extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pa.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pa.java
index eaca740..f5acd5d 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pa.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Punjabi are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_pa extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pap.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pap.java
index eaca740..cdbfbe6 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pap.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Papiamento are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_pap extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pl.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pl.java
index eaca740..3803093 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pl.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Polish are 1, paucal (few), and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_pl extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_paucal_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_paucal_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ps.java
similarity index 60%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ps.java
index eaca740..a25a701 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ps.java
@@ -13,16 +13,24 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Pashto are 1 and n, with 0 treated as plural.
+ * 
+ * TODO: verify that 0 is considered plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ps extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pt.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pt.java
index eaca740..8caee48 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pt.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Portuguese are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_pt extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pt_br.java
similarity index 62%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pt_br.java
index eaca740..78f1619 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_pt_br.java
@@ -13,16 +13,23 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Brazilian Portuguese are 1 and n, with 0 treated as
+ * singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_pt_br extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ro.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ro.java
new file mode 100644
index 0000000..a1cef7c
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ro.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Romanian are 1, 0/x01-x19, and n.
+ */
+public class DefaultRule_ro extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("few", "Count is 0 or ends in 01-19"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n == 1 ? 1 : n == 0 || (n % 100 > 0 && n % 100 < 20) ? 2 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ru.java
similarity index 61%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ru.java
index eaca740..cfdb533 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ru.java
@@ -13,16 +13,23 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Russian are x1 (but not x11), x2-x4 (but not x12-x14),
+ * and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ru extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_x1_x234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_x1_x234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sk.java
similarity index 64%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sk.java
index eaca740..93284bb 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sk.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Slovak are 1, 2-4, and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_sk extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sl.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sl.java
new file mode 100644
index 0000000..6b653ad
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sl.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * Plural forms for Slovenian are x01, x02, x03-x04, and n.
+ */
+public class DefaultRule_sl extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count ends in 01"),
+        new PluralForm("two", "Count ends in 02"),
+        new PluralForm("few", "Count ends in 03 or 04"),
+    };
+  }
+
+  @Override
+  public int select(int n) {
+    return n % 100 == 1 ? 1 : n % 100 == 2 ? 2
+        : n % 100 == 3 || n % 100 == 4 ? 3 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_so.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_so.java
index eaca740..4044548 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_so.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Somali are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_so extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sq.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sq.java
index eaca740..2b34ebb 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sq.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Albanian are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_sq extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sr.java
similarity index 61%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sr.java
index eaca740..e74fd98 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sr.java
@@ -13,16 +13,23 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Serbian are x1 (but not x11), x2-x4 (but not x12-x14),
+ * and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_sr extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_x1_x234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_x1_x234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sv.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sv.java
index eaca740..a24e34a 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sv.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Swedish are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_sv extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sw.java
similarity index 60%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sw.java
index eaca740..d1efcb6 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_sw.java
@@ -13,16 +13,24 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Swahili are 1 and n, with 0 treated as plural.
+ * 
+ * TODO: verify that 0 is considered plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_sw extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ta.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ta.java
index eaca740..4e87ff5 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ta.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Tamil are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ta extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_te.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_te.java
index eaca740..407cb52 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_te.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Telugu are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_te extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_th.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_th.java
index eaca740..f7673bd 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_th.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Thai, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_th extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ti.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ti.java
index eaca740..675b7ee 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ti.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Tigrinya are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_ti extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_tk.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_tk.java
index eaca740..f203ba2 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_tk.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Turkmen are 1 and n, with 0 treated as plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_tk extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_tr.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_tr.java
index eaca740..7bef5cf 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_tr.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Turkish, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_tr extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_uk.java
similarity index 61%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_uk.java
index eaca740..da80a53 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_uk.java
@@ -13,16 +13,23 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Ukranian are x1 (but not x11), x2-x4 (but not x12-x14),
+ * and n.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_uk extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_x1_x234_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_x1_x234_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ur.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ur.java
new file mode 100644
index 0000000..737fc83
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_ur.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Plural forms for Urdu are 1 and n, with 0 treated as plural.
+ * 
+ * TODO: verify this, as there were limited references and some disagreement.
+ */
+public class DefaultRule_ur extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_vi.java
similarity index 73%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_vi.java
index eaca740..ace452f 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_vi.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
 
 /**
- * Placeholder for generated file.
+ * There are no plural forms in Vietnamese, so just use the default.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
+public class DefaultRule_vi extends DefaultRule {
 }
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_wa.java
similarity index 63%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_wa.java
index eaca740..641472e 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_wa.java
@@ -13,16 +13,22 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Walloon are 1 and n, with 0 treated as singular.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_wa extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_01_n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_01_n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_x1_x234_n.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_x1_x234_n.java
new file mode 100644
index 0000000..c81c5ca
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_x1_x234_n.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+/**
+ * Common plural rule for languages that have singular and two plural forms,
+ * based on the units and tens digits. Some Slavic languages use this form.
+ * 
+ * @see DefaultRule_0_1_2_n
+ * @see DefaultRule_0_1_n
+ * @see DefaultRule_01_n
+ * @see DefaultRule_1_0n
+ * @see DefaultRule_1_2_n
+ * @see DefaultRule_1_234_n for the plural forms used in other Slavic languages
+ * @see DefaultRule_1_paucal_n
+ */
+public class DefaultRule_x1_x234_n {
+
+  public static PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count ends in 1 but not 11"),
+        new PluralForm("few", "Count ends in 2-4 but not 12-14"),
+    };
+  }
+
+  public static int select(int n) {
+    return n % 10 == 1 && n % 100 != 11 ? 1 : n % 10 >= 2 && n % 10 <= 4
+        && (n % 100 < 10 || n % 100 >= 20) ? 2 : 0;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_zh.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_zh.java
new file mode 100644
index 0000000..5e2feee
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_zh.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+
+/**
+ * Usually, there are no plural forms in Chinese.  When they are used,
+ * plural forms are 1 and n, with 0 treated as plural.
+ * 
+ * Plural forms are not required to be marked, but it is optional so
+ * a plural form is provided here.  No warning will be issued if it
+ * is not included, as it is acceptable to use the same form.
+ * 
+ * Also, note that this does not address the issue of using different
+ * symbols for the same digits depending on what is being counted.
+ */
+public class DefaultRule_zh extends DefaultRule {
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralFormsOptional();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_zu.java
similarity index 60%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_zu.java
index eaca740..038c2db 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/client/impl/plurals/DefaultRule_zu.java
@@ -13,16 +13,24 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
+package com.google.gwt.i18n.client.impl.plurals;
+
 
 /**
- * Placeholder for generated file.
+ * Plural forms for Zulu are 1 and n, with 0 treated as plural.
+ * 
+ * TODO: verify that 0 is considered plural.
  */
-public class CldrImpl_he extends CldrImpl {
+public class DefaultRule_zu extends DefaultRule {
 
-  public boolean isRTL() {
-    return true;
+  @Override
+  public PluralForm[] pluralForms() {
+    return DefaultRule_1_0n.pluralForms();
+  }
+
+  @Override
+  public int select(int n) {
+    return DefaultRule_1_0n.select(n);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
index f3ed87c..d78d109 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableImplCreator.java
@@ -22,16 +22,23 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.i18n.rebind.util.AbstractResource;
-import com.google.gwt.i18n.rebind.util.ResourceFactory;
+import com.google.gwt.i18n.client.LocalizableResource.Generate;
+import com.google.gwt.i18n.client.LocalizableResource.Key;
+import com.google.gwt.i18n.rebind.AnnotationsResource.AnnotationsError;
+import com.google.gwt.i18n.rebind.format.MessageCatalogFormat;
+import com.google.gwt.i18n.rebind.keygen.KeyGenerator;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
 import com.google.gwt.user.rebind.SourceWriter;
 
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
 import java.util.MissingResourceException;
-import java.util.Set;
 
 /**
  * Represents generic functionality needed for <code>Constants</code> and
@@ -40,7 +47,7 @@
 abstract class AbstractLocalizableImplCreator extends
     AbstractGeneratorClassCreator {
 
-  static String generateConstantOrMessageClass(TreeLogger logger,
+  static String generateConstantOrMessageClass(TreeLogger logger, TreeLogger deprecatedLogger,
       GeneratorContext context, String locale, JClassType targetClass)
       throws UnableToCompleteException {
     TypeOracle oracle = context.getTypeOracle();
@@ -78,15 +85,14 @@
       throw error(logger, name + " must be an interface");
     }
 
-    AbstractResource resource;
+    AbstractResource resource = null;
     try {
-      resource = ResourceFactory.getBundle(targetClass, locale);
+      resource = ResourceFactory.getBundle(logger, targetClass, locale, assignableToConstants);
     } catch (MissingResourceException e) {
       throw error(
           logger,
-          "Localization failed; there must be at least one properties file accessible through the classpath in package '"
-              + packageName
-              + "' whose base name is '"
+          "Localization failed; there must be at least one properties file accessible through"
+              + " the classpath in package '" + packageName + "' whose base name is '"
               + ResourceFactory.getResourceName(targetClass) + "'");
     } catch (IllegalArgumentException e) {
       // A bad key can generate an illegal argument exception.
@@ -95,13 +101,13 @@
 
     // generated implementations for interface X will be named X_, X_en,
     // X_en_CA, etc.
-    String realLocale = String.valueOf(ResourceFactory.LOCALE_SEPARATOR);
-    if (!ResourceFactory.DEFAULT_TOKEN.equals(resource.getLocaleName())) {
-      realLocale += resource.getLocaleName();
+    String localeSuffix = String.valueOf(ResourceFactory.LOCALE_SEPARATOR);
+    if (!ResourceFactory.DEFAULT_TOKEN.equals(locale)) {
+      localeSuffix += locale;
     }
     // Use _ rather than "." in class name, cannot use $
     String resourceName = targetClass.getName().replace('.', '_');
-    String className = resourceName + realLocale;
+    String className = resourceName + localeSuffix;
     PrintWriter pw = context.tryCreate(logger, packageName, className);
     if (pw != null) {
       ClassSourceFileComposerFactory factory = new ClassSourceFileComposerFactory(
@@ -111,38 +117,119 @@
       // Now that we have all the information set up, process the class
       if (constantsWithLookupClass.isAssignableFrom(targetClass)) {
         ConstantsWithLookupImplCreator c = new ConstantsWithLookupImplCreator(
-            logger, writer, targetClass, resource, context.getTypeOracle());
-        c.emitClass(logger);
+            logger, deprecatedLogger, writer, targetClass, resource, context.getTypeOracle());
+        c.emitClass(logger, locale);
       } else if (constantsClass.isAssignableFrom(targetClass)) {
-        ConstantsImplCreator c = new ConstantsImplCreator(logger, writer,
+        ConstantsImplCreator c = new ConstantsImplCreator(logger, deprecatedLogger, writer,
             targetClass, resource, context.getTypeOracle());
-        c.emitClass(logger);
+        c.emitClass(logger, locale);
       } else {
-        MessagesImplCreator messages = new MessagesImplCreator(logger, writer,
+        MessagesImplCreator messages = new MessagesImplCreator(logger, deprecatedLogger, writer,
             targetClass, resource, context.getTypeOracle());
-        messages.emitClass(logger);
+        messages.emitClass(logger, locale);
       }
       context.commit(logger, pw);
     }
+    // Generate a translatable output file if requested.
+    Generate generate = targetClass.getAnnotation(Generate.class);
+    if (generate != null) {
+      try {
+        String path = "no-deploy" + File.pathSeparator + generate.fileName();
+        if (Generate.DEFAULT.equals(path)) {
+          path = targetClass.getPackage().getName() + "."
+          + targetClass.getName().replace('.', '_');
+        } else if (path.endsWith(File.pathSeparator)) {
+          path = path + targetClass.getName().replace('.', '_');
+        }
+        String[] genLocales = generate.locales();
+        boolean found = false;
+        if (genLocales.length != 0) {
+          // verify the current locale is in the list
+          for (String genLocale : genLocales) {
+            if (genLocale.equals(locale)) {
+              found = true;
+              break;
+            }
+          }
+        } else {
+          // Since they want all locales, this is guaranteed to be one of them.
+          found = true;
+        }
+        if (found) {
+          for (String genClassName : generate.format()) {
+            Class<? extends MessageCatalogFormat> msgFormatClass = Class.forName(
+                genClassName).asSubclass(MessageCatalogFormat.class);
+            MessageCatalogFormat msgWriter = msgFormatClass.newInstance();
+            // Make generator-specific changes to a temporary copy of the path.
+            String genPath = path;
+            if (genLocales.length != 1) {
+              // If the user explicitly specified only one locale, do not add the locale.
+              genPath += '_' + locale;
+            }
+            genPath += msgWriter.getExtension();
+            OutputStream outStr = context.tryCreateResource(logger, genPath);
+            if (outStr != null) {
+              logger.log(TreeLogger.INFO, "Generating " + genPath + " from "
+                  + className + " for locale " + locale, null);
+              PrintWriter out = new PrintWriter(new BufferedWriter(
+                  new OutputStreamWriter(outStr, "UTF-8")), false);
+              msgWriter.write(logger, resource, out, targetClass);
+              out.flush();
+              context.commitResource(logger, outStr);
+            }
+          }
+        }
+      } catch (InstantiationException e) {
+      } catch (IllegalAccessException e) {
+      } catch (ClassNotFoundException e) {
+      } catch (UnsupportedEncodingException e) {
+        throw error(logger, e.getMessage());
+      }
+    }
     return packageName + "." + className;
   }
 
   /**
+   * Generator to use to create keys for messages.
+   */
+  private KeyGenerator keyGenerator;
+
+  /**
    * The Dictionary/value bindings used to determine message contents.
    */
   private AbstractResource messageBindings;
 
   /**
+   * Logger to use for deprecated warnings.
+   */
+  private TreeLogger deprecatedLogger;
+
+  /**
+   * True if the class being generated uses Constants-style annotations/quoting.
+   */
+  private boolean isConstants;
+  
+  /**
    * Constructor for <code>AbstractLocalizableImplCreator</code>.
    * 
+   * @param deprecatedLogger logger to use for deprecated warnings.
    * @param writer writer
    * @param targetClass current target
    * @param messageBindings backing resource
    */
-  public AbstractLocalizableImplCreator(SourceWriter writer,
-      JClassType targetClass, AbstractResource messageBindings) {
+  public AbstractLocalizableImplCreator(TreeLogger logger, TreeLogger deprecatedLogger,
+      SourceWriter writer, JClassType targetClass, AbstractResource messageBindings,
+      boolean isConstants) {
     super(writer, targetClass);
+    this.deprecatedLogger = deprecatedLogger;
     this.messageBindings = messageBindings;
+    this.isConstants = isConstants;
+    try {
+      keyGenerator = AnnotationsResource.getKeyGenerator(targetClass);
+    } catch (AnnotationsError e) {
+      logger.log(TreeLogger.WARN, "Error getting key generator for "
+          + targetClass.getQualifiedSourceName(), e);
+    }
   }
 
   /**
@@ -163,52 +250,59 @@
    * Find the creator associated with the given method, and delegate the
    * creation of the method body to it.
    * 
-   * @param logger
+   * @param logger TreeLogger instance for logging
    * @param method method to be generated
+   * @param locale locale to generate
    * @throws UnableToCompleteException
    */
-  protected void delegateToCreator(TreeLogger logger, JMethod method)
+  protected void delegateToCreator(TreeLogger logger, JMethod method, String locale)
       throws UnableToCompleteException {
     AbstractMethodCreator methodCreator = getMethodCreator(logger, method);
     String key = getKey(logger, method);
-    String value;
-    try {
-      value = messageBindings.getString(key);
-    } catch (MissingResourceException e) {
-      String s = "Could not find requested resource key '" + key + "'";
-      TreeLogger child = logger.branch(TreeLogger.ERROR, s, null);
-      Set<String> keys = messageBindings.keySet();
-      if (keys.size() < AbstractResource.REPORT_KEYS_THRESHOLD) {
-        String keyString = "Keys found: " + keys;
-        throw error(child, keyString);
-      } else {
-        throw new UnableToCompleteException();
-      }
+    if (key == null) {
+      logger.log(TreeLogger.ERROR, "Unable to get or compute key for method " + method.getName(),
+          null);
+      throw new UnableToCompleteException();
     }
-    String info = "When locale is '" + messageBindings.getLocaleName()
-        + "', property '" + key + "' has the value '" + value + "'";
-    TreeLogger branch = logger.branch(TreeLogger.TRACE, info, null);
-    methodCreator.createMethodFor(branch, method, value);
+    methodCreator.createMethodFor(logger, method, key, messageBindings, locale);
   }
 
   /**
    * Returns a resource key given a method name.
    * 
-   * @param logger
-   * @param method
-   * @return the key
+   * @param logger TreeLogger instance for logging
+   * @param method method to get key for
+   * @return the key to use for resource lookups or null if unable to get
+   *     or compute the key
    */
   protected String getKey(TreeLogger logger, JMethod method) {
+    Key key = method.getAnnotation(Key.class);
+    if (key != null) {
+      return key.value();
+    }
     String[][] id = method.getMetaData(LocalizableGenerator.GWT_KEY);
     if (id.length > 0) {
+      warnOnMetadata(method);
       if (id[0].length == 0) {
         logger.log(TreeLogger.WARN, method
-            + " had a mislabeled gwt.key, using method name as key", null);
+            + " had a mislabeled gwt.key, using default key", null);
       } else {
         String tag = id[0][0];
         return tag;
       }
     }
-    return method.getName();
+    return AnnotationsResource.getKey(logger, keyGenerator, method, isConstants);
   }
+  
+  /**
+   * Issue a warning about deprecated metadata.
+   * 
+   * @param method method to warn about
+   */
+  void warnOnMetadata(JMethod method) {
+    deprecatedLogger.log(TreeLogger.WARN, "Deprecated metadata found on "
+        + method.getEnclosingType().getSimpleSourceName() + "."
+        + method.getName() + ";svn use annotations instead", null);
+  }
+
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/util/AbstractLocalizableInterfaceCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableInterfaceCreator.java
similarity index 97%
rename from user/src/com/google/gwt/i18n/rebind/util/AbstractLocalizableInterfaceCreator.java
rename to user/src/com/google/gwt/i18n/rebind/AbstractLocalizableInterfaceCreator.java
index ae3838c..e0de628 100644
--- a/user/src/com/google/gwt/i18n/rebind/util/AbstractLocalizableInterfaceCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableInterfaceCreator.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.rebind.util;
+package com.google.gwt.i18n.rebind;
 
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
@@ -152,6 +152,7 @@
    */
   protected abstract String javaDocComment(String path);
 
+  @SuppressWarnings("unchecked") // use of raw type from LocalizedProperties
   void generateFromPropertiesFile() throws IOException {
     InputStream propStream = new FileInputStream(resourceFile);
     LocalizedProperties p = new LocalizedProperties();
@@ -159,7 +160,7 @@
     addFormatters();
     // TODO: Look for a generic version of Tapestry's LocalizedProperties class
     Iterator<Entry<String, String>> elements =
-      p.getPropertyMap().entrySet().iterator();
+      p.getPropertyMap().entrySet().iterator(); // suppress warnings
     if (elements.hasNext() == false) {
       throw new IllegalStateException(
           "File '"
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java
index 026a7b3..260cd82 100644
--- a/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractLocalizableMethodCreator.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.i18n.rebind;
 
-import com.google.gwt.i18n.rebind.util.AbstractResource;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 
diff --git a/user/src/com/google/gwt/i18n/rebind/AbstractResource.java b/user/src/com/google/gwt/i18n/rebind/AbstractResource.java
new file mode 100644
index 0000000..cdb3e01
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/AbstractResource.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2008 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.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * AbstractResource serves the same purpose as java
+ * ResourceBundle/PropertyResourceBundle.
+ * <p>
+ * Each <code>Resource</code> belongs to a resource tree, indicated by the
+ * path attribute.
+ * <p>
+ * AbstractResource uses a Factory pattern rather than a single static method to
+ * load itself given an abstract string path.
+ * <p>
+ * One advanced feature which should not be used outside the core GWT system is
+ * that resources can have more than one parent, for instance pets_en_US could
+ * have pets_en as one parent and animals_en_US as another. The alternative
+ * parents have lower precedence than any primary parent. Each alternative
+ * parent is associated with a separate resource tree.
+ */
+public abstract class AbstractResource {
+  /**
+   * Exception indicating a require resource was not found.
+   */
+  public static class MissingResourceException extends RuntimeException {
+    private String key;
+    private String method;
+    private List<AbstractResource> searchedResources;
+    private String during;
+
+    public MissingResourceException(String key,
+        List<AbstractResource> searchedResources) {
+      super("No resource found for key '" + key + "'");
+      this.key = key;
+      this.searchedResources = searchedResources;
+    }
+
+    public String getDuring() {
+      return during;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public String getMethod() {
+      return method;
+    }
+
+    public List<AbstractResource> getSearchedResources() {
+      return searchedResources;
+    }
+
+    public void setDuring(String during) {
+      this.during = during;
+    }
+
+    public void setMethod(String method) {
+      this.method = method;
+    }
+  }
+
+  /**
+   * Error messages concerning missing keys should include the defined keys if
+   * the number of keys is below this threshold.
+   */
+  public static final int REPORT_KEYS_THRESHOLD = 30;
+
+  protected static String getExtendedKey(String key, String extension) {
+    if (extension != null) {
+      key += '[' + extension + ']';
+    }
+    return key;
+  }
+
+  private final List<AbstractResource> alternativeParents = new ArrayList<AbstractResource>();
+
+  private Set<String> keySet;
+
+  private Map<String, PluralForm[]> pluralFormMap = new HashMap<String, PluralForm[]>();
+
+  private String localeName;
+
+  private String path;
+
+  private AbstractResource primaryParent;
+
+  /**
+   * Walk up the resource inheritance tree until we find one which is an
+   * instance of AnnotationsResource.
+   * 
+   * This is needed so the original Annotations metadata can be used even in
+   * inherited resources, such as from a properties file for a specific locale.
+   * 
+   * TODO(jat): really bad code smell -- the superclass should not know about a
+   * particular implementation, but I couldn't think of a decent way around it.
+   * Long term, this whole structure needs to be be redone to make use of
+   * resources that fundamentally don't look like property files to be used
+   * effectively. I don't think it is feasible to do that in time for 1.5, so I
+   * am continuing with this hacky solution but we need to look at this after
+   * 1.5 is done.
+   */
+  public AnnotationsResource getAnnotationsResource() {
+    AbstractResource resource = this;
+    while (resource != null && !(resource instanceof AnnotationsResource)) {
+      resource = resource.primaryParent;
+    }
+    return (AnnotationsResource) resource;
+  }
+
+  public Collection<String> getExtensions(String key) {
+    return new ArrayList<String>();
+  }
+
+  /**
+   * @see java.util.ResourceBundle#getLocale()
+   */
+  public String getLocaleName() {
+    return localeName;
+  }
+
+  /**
+   * @see java.util.ResourceBundle#getObject(java.lang.String)
+   */
+  public final Object getObject(String key) {
+    Object s = getObjectAux(key, true, true);
+    return s;
+  }
+
+  public PluralForm[] getPluralForms(String key) {
+    return pluralFormMap.get(key);
+  }
+
+  /**
+   * Get a string (with optional extension) and fail if not present.
+   * 
+   * @param logger
+   * @param key
+   * @param extension
+   * @return the requested string
+   */
+  public final String getRequiredStringExt(TreeLogger logger, String key,
+      String extension) {
+    return extension == null ? (String) getObjectAux(key, true, true) : null;
+  }
+
+  /**
+   * @see java.util.ResourceBundle#getString(java.lang.String)
+   */
+  public final String getString(String key) {
+    return (String) getObjectAux(key, true);
+  }
+
+  /**
+   * Get a key with an extension. Identical to getString() if extension is null.
+   * 
+   * @param key to lookup
+   * @param extension extension of the key, nullable
+   * @return string or null
+   */
+  public String getStringExt(String key, String extension) {
+    return extension == null ? getString(key) : null;
+  }
+
+  /**
+   * Keys associated with this resource.
+   * 
+   * @return keys
+   */
+  public Set<String> keySet() {
+    if (keySet == null) {
+      keySet = new HashSet<String>();
+      addToKeySet(keySet);
+      if (primaryParent != null) {
+        primaryParent.addToKeySet(keySet);
+      }
+      for (int i = 0; i < alternativeParents.size(); i++) {
+        AbstractResource element = alternativeParents.get(i);
+        keySet.addAll(element.keySet());
+      }
+    }
+    return keySet;
+  }
+
+  public void setPluralForms(String key, PluralForm[] pluralForms) {
+    pluralFormMap.put(key, pluralForms);
+  }
+
+  @Override
+  public String toString() {
+    return "resource for " + path;
+  }
+
+  /**
+   * A multi-line representation of this object.
+   * 
+   * @return verbose string
+   */
+  public String toVerboseString() {
+    StringBuffer b = new StringBuffer();
+    toVerboseStringAux(0, b);
+    return b.toString();
+  }
+
+  void addAlternativeParent(AbstractResource parent) {
+    if (parent != null) {
+      alternativeParents.add(parent);
+    }
+  }
+
+  abstract void addToKeySet(Set<String> s);
+
+  void checkKeys() {
+    // If I don't have a parent, then I am a default node so do not need to
+    // conform
+    if (primaryParent == null) {
+      return;
+    }
+    for (String key : keySet()) {
+      if (primaryParent.getObjectAux(key, true) == null) {
+        for (int i = 0; i < alternativeParents.size(); i++) {
+          AbstractResource alt = alternativeParents.get(i);
+          if (alt.getObjectAux(key, true) != null) {
+            break;
+          }
+        }
+
+        throw new IllegalArgumentException(
+            key
+                + " is not a valid resource key as it does not occur in the default version of "
+                + this + " nor in any of " + alternativeParents);
+      }
+    }
+  }
+
+  final Object getObjectAux(String key, boolean useAlternativeParents) {
+    try {
+      return getObjectAux(key, useAlternativeParents, false);
+    } catch (MissingResourceException e) {
+      // Can't happen since we pass required=false
+      throw new RuntimeException("Unexpected MissingResourceException", e);
+    }
+  }
+
+  final Object getObjectAux(String key, boolean useAlternativeParents,
+      boolean required) throws MissingResourceException {
+    ArrayList<AbstractResource> searched = new ArrayList<AbstractResource>();
+    searched.add(this);
+    Object s = handleGetObject(key);
+    if (s != null) {
+      return s;
+    }
+    AbstractResource parent = this.getPrimaryParent();
+    if (parent != null) {
+      // Primary parents should not look at their alternative parents
+      searched.add(parent);
+      s = parent.getObjectAux(key, false);
+    }
+    if ((s == null) && (alternativeParents.size() > 0)
+        && (useAlternativeParents)) {
+      for (int i = 0; (i < alternativeParents.size()) && (s == null); i++) {
+        // Alternate parents may look at their alternative parents.
+        AbstractResource altParent = alternativeParents.get(i);
+        searched.add(altParent);
+        s = altParent.getObjectAux(key, true);
+      }
+    }
+    if (s == null && required) {
+      throw new MissingResourceException(key, searched);
+    }
+    return s;
+  }
+
+  String getPath() {
+    return path;
+  }
+
+  AbstractResource getPrimaryParent() {
+    return primaryParent;
+  }
+
+  abstract Object handleGetObject(String key);
+
+  void setLocaleName(String locale) {
+    this.localeName = locale;
+  }
+
+  void setPath(String path) {
+    this.path = path;
+  }
+
+  void setPrimaryParent(AbstractResource primaryParent) {
+    if (primaryParent == null) {
+      return;
+    }
+    this.primaryParent = primaryParent;
+  }
+
+  private void newLine(int indent, StringBuffer buf) {
+    buf.append("\n");
+    for (int i = 0; i < indent; i++) {
+      buf.append("\t");
+    }
+  }
+
+  private void toVerboseStringAux(int indent, StringBuffer buf) {
+    newLine(indent, buf);
+    buf.append(toString());
+    if (primaryParent != null) {
+      newLine(indent, buf);
+      buf.append("Primary Parent: ");
+      primaryParent.toVerboseStringAux(indent + 1, buf);
+    }
+    for (int i = 0; i < alternativeParents.size(); i++) {
+      newLine(indent, buf);
+      buf.append("Alternate Parent: ");
+      AbstractResource element = alternativeParents.get(i);
+      element.toVerboseStringAux(indent + 1, buf);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java b/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
new file mode 100644
index 0000000..f912787
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/AnnotationsResource.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright 2008 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.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JRawType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.i18n.client.Constants.DefaultBooleanValue;
+import com.google.gwt.i18n.client.Constants.DefaultDoubleValue;
+import com.google.gwt.i18n.client.Constants.DefaultFloatValue;
+import com.google.gwt.i18n.client.Constants.DefaultIntValue;
+import com.google.gwt.i18n.client.Constants.DefaultStringArrayValue;
+import com.google.gwt.i18n.client.Constants.DefaultStringMapValue;
+import com.google.gwt.i18n.client.Constants.DefaultStringValue;
+import com.google.gwt.i18n.client.LocalizableResource.Description;
+import com.google.gwt.i18n.client.LocalizableResource.GenerateKeys;
+import com.google.gwt.i18n.client.LocalizableResource.Key;
+import com.google.gwt.i18n.client.LocalizableResource.Meaning;
+import com.google.gwt.i18n.client.Messages.DefaultMessage;
+import com.google.gwt.i18n.client.Messages.Example;
+import com.google.gwt.i18n.client.Messages.Optional;
+import com.google.gwt.i18n.client.Messages.PluralCount;
+import com.google.gwt.i18n.client.Messages.PluralText;
+import com.google.gwt.i18n.rebind.keygen.KeyGenerator;
+import com.google.gwt.i18n.rebind.keygen.MethodNameKeyGenerator;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * AbstractResource implementation which looks up text annotations
+ * on classes.
+ */
+public class AnnotationsResource extends AbstractResource {
+
+  /**
+   * An exception indicating there was some problem with an annotation.
+   * 
+   * A caller receiving this exception should log the human-readable
+   * message and treat it as a fatal error.
+   */
+  public static class AnnotationsError extends Exception {
+    
+    public AnnotationsError(String msg) {
+      super(msg);
+    }
+  }
+
+  /**
+   * Class for argument information, used for export.
+   */
+  public static class ArgumentInfo {
+    public String name;
+    public boolean optional;
+    public boolean isPluralCount;
+    public String example;
+    
+    public ArgumentInfo(String name) {
+      this.name = name;
+    }
+  }
+  
+  /**
+   * Class to keep annotation information about a particular method.
+   */
+  private static class MethodEntry {
+    
+    public String text;
+    public String meaning;
+    public String description;
+    public Map<String, String> pluralText;
+    public ArrayList<ArgumentInfo> arguments;
+    
+    public MethodEntry(String text, String meaning) {
+      this.text = text;
+      this.meaning = meaning;
+      pluralText = new HashMap<String, String>();
+      arguments = new ArrayList<ArgumentInfo>();
+    }
+    
+    public ArgumentInfo addArgument(String argName) {
+      ArgumentInfo argInfo = new ArgumentInfo(argName);
+      arguments.add(argInfo);
+      return argInfo;
+    }
+    
+    public void addPluralText(String form, String text) {
+      pluralText.put(form, text);
+    }
+  }
+
+  /**
+   * Returns the key for a given method.
+   * 
+   * If null is returned, an error message has already been logged.
+   * 
+   * @return null if unable to get or compute the key for this method,
+   *     otherwise the key is returned
+   */
+  public static String getKey(TreeLogger logger, KeyGenerator keyGenerator, JMethod method, boolean isConstants) {
+    Key key = method.getAnnotation(Key.class);
+    if (key != null) {
+      return key.value();
+    }
+    String text;
+    try {
+      text = getTextString(method, null, isConstants);
+    } catch (AnnotationsError e) {
+      return null;
+    }
+    String meaningString = null;
+    Meaning meaning = method.getAnnotation(Meaning.class);
+    if (meaning != null) {
+      meaningString = meaning.value();
+    }
+    String keyStr = keyGenerator.generateKey(method.getEnclosingType().getQualifiedSourceName(),
+        method.getName(), text, meaningString);
+    if (keyStr == null) {
+      if (text == null) {
+        logger.log(TreeLogger.ERROR, "Key generator " + keyGenerator.getClass().getName()
+            + " requires the default value be specified in an annotation for method "
+            + method.getName(), null);
+      } else {
+        logger.log(TreeLogger.ERROR, "Key generator " + keyGenerator.getClass().getName()
+            + " was unable to compute a key value for method " + method.getName(), null);
+      }
+    }
+    return keyStr;
+  }
+
+  /**
+   * Returns a suitable key generator for the specified class.
+   * @throws AnnotationsError 
+   */
+  public static KeyGenerator getKeyGenerator(JClassType targetClass)
+      throws AnnotationsError {
+    GenerateKeys generator = targetClass.getAnnotation(GenerateKeys.class);
+    if (generator != null) {
+      String className = generator.value();
+      try {
+        Class<? extends KeyGenerator> keyGeneratorClass
+            = Class.forName(className).asSubclass(KeyGenerator.class);
+        return keyGeneratorClass.newInstance();
+      } catch (InstantiationException e) {
+        throw new AnnotationsError("@GenerateKeys: unable to instantiate " + className);
+      } catch (IllegalAccessException e) {
+        throw new AnnotationsError("@GenerateKeys: unable to instantiate " + className);
+      } catch (ClassNotFoundException e) {
+        throw new AnnotationsError("Invalid class specified to @GenerateKeys: "
+            + className);
+      }
+    }
+    return new MethodNameKeyGenerator();
+  }
+
+  /**
+   * Return the text string from annotations for a particular method.
+   * 
+   * @param method the method to retrieve text
+   * @param map if not null, add keys for DefaultStringMapValue to this map
+   * @param isConstants true if the method is in a subinterface of Constants
+   * @throws AnnotationsError if the annotation usage is incorrect
+   * @return the text value to use for this method, as if read from a properties
+   *         file, or null if there are no annotations.
+   */
+  private static String getTextString(JMethod method,
+      Map<String, MethodEntry> map, boolean isConstants) throws AnnotationsError {
+    JType returnType = method.getReturnType();
+    DefaultMessage defaultText = method.getAnnotation(DefaultMessage.class);
+    DefaultStringValue stringValue = method.getAnnotation(DefaultStringValue.class);
+    DefaultStringArrayValue stringArrayValue = method.getAnnotation(
+        DefaultStringArrayValue.class);
+    DefaultStringMapValue stringMapValue = method.getAnnotation(DefaultStringMapValue.class);
+    DefaultIntValue intValue = method.getAnnotation(DefaultIntValue.class);
+    DefaultFloatValue floatValue = method.getAnnotation(DefaultFloatValue.class);
+    DefaultDoubleValue doubleValue = method.getAnnotation(DefaultDoubleValue.class);
+    DefaultBooleanValue booleanValue = method.getAnnotation(DefaultBooleanValue.class);
+    int constantsCount = 0;
+    if (stringValue != null) {
+      constantsCount++;
+      if (!returnType.getQualifiedSourceName().equals("java.lang.String")) {
+        throw new AnnotationsError(
+            "@DefaultStringValue can only be used with a method returning String");
+      }
+    }
+    if (stringArrayValue != null) {
+      constantsCount++;
+      JArrayType arrayType = returnType.isArray();
+      if (arrayType == null
+          || !arrayType.getComponentType().getQualifiedSourceName().equals("java.lang.String")) {
+        throw new AnnotationsError(
+            "@DefaultStringArrayValue can only be used with a method returning String[]");
+      }
+    }
+    if (stringMapValue != null) {
+      constantsCount++;
+      JRawType rawType = returnType.getErasedType().isRawType();
+      boolean error = false;
+      if (rawType == null || !rawType.getQualifiedSourceName().equals("java.util.Map")) {
+        error = true;
+      } else {
+        JParameterizedType paramType = returnType.isParameterized();
+        if (paramType != null) {
+          JType[] args = paramType.getTypeArgs();
+          if (args.length != 2 || !args[0].getQualifiedSourceName().equals("java.lang.String")
+             || !args[1].getQualifiedSourceName().equals("java.lang.String")) {
+            error = true;
+          }
+        }
+      }
+      if (error) {
+        throw new AnnotationsError("@DefaultStringMapValue can only be used with a method "
+            + "returning Map or Map<String,String>");
+      }
+    }
+    if (intValue != null) {
+      constantsCount++;
+      JPrimitiveType primType = returnType.isPrimitive();
+      if (primType != JPrimitiveType.INT) {
+        throw new AnnotationsError(
+        "@DefaultIntValue can only be used with a method returning int");
+      }
+    }
+    if (floatValue != null) {
+      constantsCount++;
+      JPrimitiveType primType = returnType.isPrimitive();
+      if (primType != JPrimitiveType.FLOAT) {
+        throw new AnnotationsError(
+        "@DefaultFloatValue can only be used with a method returning float");
+      }
+    }
+    if (doubleValue != null) {
+      constantsCount++;
+      JPrimitiveType primType = returnType.isPrimitive();
+      if (primType != JPrimitiveType.DOUBLE) {
+        throw new AnnotationsError(
+        "@DefaultDoubleValue can only be used with a method returning double");
+      }
+    }
+    if (booleanValue != null) {
+      constantsCount++;
+      JPrimitiveType primType = returnType.isPrimitive();
+      if (primType != JPrimitiveType.BOOLEAN) {
+        throw new AnnotationsError(
+        "@DefaultBooleanValue can only be used with a method returning boolean");
+      }
+    }
+    if (!isConstants) {
+      if (constantsCount > 0) {
+        throw new AnnotationsError(
+          "@Default*Value is not permitted on a Messages interface; see @DefaultText");
+      }
+      if (defaultText != null) {
+        return defaultText.value();
+      }
+    } else {
+      if (defaultText != null) {
+        throw new AnnotationsError(
+          "@DefaultText is not permitted on a Constants interface; see @Default*Value");
+      }
+      if (constantsCount > 1) {
+        throw new AnnotationsError(
+          "No more than one @Default*Value annotation may be used on a method");
+      }
+      if (stringValue != null) {
+        return stringValue.value();
+      } else if (intValue != null) {
+        return Integer.toString(intValue.value());
+      } else if (floatValue != null) {
+        return Float.toString(floatValue.value());
+      } else if (doubleValue != null) {
+        return Double.toString(doubleValue.value());
+      } else if (booleanValue != null) {
+        return Boolean.toString(booleanValue.value());
+      } else if (stringArrayValue != null) {
+        StringBuilder buf = new StringBuilder();
+        boolean firstString = true;
+        for (String str : stringArrayValue.value()) {
+          str = str.replace("\\", "\\\\");
+          str = str.replace(",", "\\,");
+          if (!firstString) {
+            buf.append(',');
+          } else {
+            firstString = false;
+          }
+          buf.append(str);
+        }
+        return buf.toString();
+      } else if (stringMapValue != null) {
+        StringBuilder buf = new StringBuilder();
+        boolean firstString = true;
+        String[] entries = stringMapValue.value();
+        if ((entries.length & 1) != 0) {
+          throw new AnnotationsError("Odd number of strings supplied to @DefaultStringMapValue");
+        }
+        for (int i = 0; i < entries.length; i += 2) {
+          String key = entries[i];
+          String value = entries[i + 1];
+          
+          if (map != null) {
+            // add key=value part to map
+            MethodEntry entry = new MethodEntry(value, null);
+            map.put(key, entry);
+          }
+          
+          // add the key to the master entry
+          key = key.replace("\\", "\\\\");
+          key = key.replace(",", "\\,");
+          if (!firstString) {
+            buf.append(',');
+          } else {
+            firstString = false;
+          }
+          buf.append(key);
+        }
+        return buf.toString();
+      }
+    }
+    return null;
+  }
+  
+  private Map<String, MethodEntry> map;
+
+  /**
+   * Create a resource that supplies data from i18n-related annotations.
+   * 
+   * @param logger 
+   * @param clazz
+   * @param isConstants 
+   * @throws AnnotationsError if there is a fatal error while processing annotations
+   */
+  public AnnotationsResource(TreeLogger logger, JClassType clazz, boolean isConstants)
+      throws AnnotationsError {
+    KeyGenerator keyGenerator = getKeyGenerator(clazz);
+    map = new HashMap<String, MethodEntry>();
+    for (JMethod method : clazz.getMethods()) {
+      String meaningString = null;
+      Meaning meaning = method.getAnnotation(Meaning.class);
+      if (meaning != null) {
+        meaningString = meaning.value();
+      }
+      String textString = getTextString(method, map, isConstants);
+      if (textString == null) {
+        // ignore ones without some value annotation
+        continue;
+      }
+      String key = null;
+      Key keyAnnot = method.getAnnotation(Key.class);
+      if (keyAnnot != null) {
+        key = keyAnnot.value();
+      } else {
+        key = keyGenerator.generateKey(method.getEnclosingType().getQualifiedSourceName(),
+            method.getName(), textString, meaningString);
+      }
+      if (key == null) {
+        throw new AnnotationsError("Could not compute key for "
+            + method.getEnclosingType().getQualifiedSourceName() + "." + method.getName());
+      }
+      MethodEntry entry = new MethodEntry(textString, meaningString);
+      map.put(key, entry);
+      Description description = method.getAnnotation(Description.class);
+      if (description != null) {
+        entry.description = description.value();
+      }
+      PluralText pluralText = method.getAnnotation(PluralText.class);
+      if (pluralText != null) {
+        String[] pluralForms = pluralText.value();
+        if ((pluralForms.length & 1) != 0) {
+          throw new AnnotationsError("Odd number of strings supplied to @PluralText: must be"
+              + " pairs of form names and strings");
+        }
+        for (int i = 0; i + 1 < pluralForms.length; i += 2) {
+          entry.pluralText.put(pluralForms[i], pluralForms[i + 1]);
+        }
+      }
+      for (JParameter param : method.getParameters()) {
+        ArgumentInfo argInfo = entry.addArgument(param.getName());
+        Optional optional = param.getAnnotation(Optional.class);
+        if (optional != null) {
+          argInfo.optional = true;
+        }
+        PluralCount pluralCount = param.getAnnotation(PluralCount.class);
+        if (pluralCount != null) {
+          argInfo.isPluralCount = true;
+        }
+        Example example = param.getAnnotation(Example.class);
+        if (example != null) {
+          argInfo.example = example.value();
+        }
+      }
+    }
+    setPath(clazz.getQualifiedSourceName());
+  }
+
+  @Override
+  public void addToKeySet(Set<String> s) {
+    s.addAll(map.keySet());
+  }
+
+  public Iterable<ArgumentInfo> argumentsIterator(String key) {
+    MethodEntry entry = map.get(key);
+    return entry != null ? entry.arguments : null;
+  }
+  
+  public String getDescription(String key) {
+    MethodEntry entry = map.get(key);
+    return entry == null ? null : entry.description;
+  }
+
+  @Override
+  public Collection<String> getExtensions(String key) {
+    MethodEntry entry = map.get(key);
+    return entry == null ? new ArrayList<String>() : entry.pluralText.keySet();
+  }
+  
+  public String getMeaning(String key) {
+    MethodEntry entry = map.get(key);
+    return entry == null ? null : entry.meaning;
+  }
+  
+  @Override
+  public String getStringExt(String key, String extension) {
+    if (extension == null) {
+      return getString(key);
+    }
+    MethodEntry entry = map.get(key);
+    return entry == null ? null : entry.pluralText.get(extension);
+  }
+  
+  public boolean notEmpty() {
+    return !map.isEmpty();
+  }
+  
+  public void setParentResource(AnnotationsResource parent) {
+    setPrimaryParent(parent);
+  }
+
+  @Override
+  public String toString() {
+    return "Annotations from class " + getPath();
+  }
+  
+  @Override
+  Object handleGetObject(String key) {
+    MethodEntry entry = map.get(key);
+    return entry == null ? null : entry.text;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java
index d1b467c..65a80df 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsImplCreator.java
@@ -23,7 +23,6 @@
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.TypeOracleException;
-import com.google.gwt.i18n.rebind.util.AbstractResource;
 import com.google.gwt.user.rebind.SourceWriter;
 
 import java.util.Map;
@@ -42,16 +41,17 @@
    * Constructor for <code>ConstantsImplCreator</code>.
    * 
    * @param logger logger to print errors
+   * @param deprecatedLogger logger to use for deprecated warnings
    * @param writer <code>Writer</code> to print to
    * @param localizableClass class/interface to conform to
    * @param messageBindings resource bundle used to generate the class
    * @param oracle types
    * @throws UnableToCompleteException
    */
-  public ConstantsImplCreator(TreeLogger logger, SourceWriter writer,
+  public ConstantsImplCreator(TreeLogger logger, TreeLogger deprecatedLogger, SourceWriter writer,
       JClassType localizableClass, AbstractResource messageBindings,
       TypeOracle oracle) throws UnableToCompleteException {
-    super(writer, localizableClass, messageBindings);
+    super(logger, deprecatedLogger, writer, localizableClass, messageBindings, true);
     try {
       JClassType stringClass = oracle.getType(String.class.getName());
       JClassType mapClass = oracle.getType(Map.class.getName());
@@ -93,10 +93,10 @@
    * arg0...argN.
    */
   @Override
-  protected void emitMethodBody(TreeLogger logger, JMethod method)
+  protected void emitMethodBody(TreeLogger logger, JMethod method, String locale)
       throws UnableToCompleteException {
     checkConstantMethod(logger, method);
-    delegateToCreator(logger, method);
+    delegateToCreator(logger, method, locale);
   }
 
   boolean isNeedCache() {
diff --git a/user/src/com/google/gwt/i18n/rebind/util/ConstantsInterfaceCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsInterfaceCreator.java
similarity index 97%
rename from user/src/com/google/gwt/i18n/rebind/util/ConstantsInterfaceCreator.java
rename to user/src/com/google/gwt/i18n/rebind/ConstantsInterfaceCreator.java
index 5720f9d..d3a3431 100644
--- a/user/src/com/google/gwt/i18n/rebind/util/ConstantsInterfaceCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsInterfaceCreator.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.rebind.util;
+package com.google.gwt.i18n.rebind;
 
 import com.google.gwt.i18n.client.Constants;
 
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
index 37afc6b..5c0fd3e 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsMapMethodCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -18,10 +18,10 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.i18n.client.impl.ConstantMap;
+import com.google.gwt.i18n.rebind.AbstractResource.MissingResourceException;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 
-import java.util.MissingResourceException;
-
 /**
  * Creator for methods of the form Map getX() .
  */
@@ -43,15 +43,15 @@
    * @throws UnableToCompleteException
    */
   @Override
-  public void createMethodFor(TreeLogger logger, JMethod method,
-      final String value) throws UnableToCompleteException {
+  public void createMethodFor(TreeLogger logger, JMethod method, String key,
+      AbstractResource resource, String locale) throws UnableToCompleteException {
     String methodName = method.getName();
-
     if (method.getParameters().length > 0) {
       error(
           logger,
           methodName
-              + " cannot have parameters; extend Messages instead if you need to create parameterized messages");
+              + " cannot have parameters; extend Messages instead if you need to create "
+              + "parameterized messages");
     }
     // make sure cache exists
     enableCache();
@@ -61,18 +61,24 @@
     // if not found create Map
     println("if (args == null){");
     indent();
-    println("args = new com.google.gwt.i18n.client.impl.ConstantMap();");
+    println("args = new " + ConstantMap.class.getCanonicalName() + "();");
+    String value;
+    try {
+      value = resource.getRequiredStringExt(logger, key, null);
+    } catch (MissingResourceException e) {
+      e.setDuring("getting key list");
+      throw e;
+    }
     String[] args = ConstantsStringArrayMethodCreator.split(value);
 
     for (int i = 0; i < args.length; i++) {
       try {
-        String key = args[i];
+        key = args[i];
         String keyValue = getResources().getString(key);
         println("args.put(" + wrap(key) + ", " + wrap(keyValue) + ");");
       } catch (MissingResourceException e) {
-        String msg = "While implementing map for " + method.getName()
-            + "(), could not find key '" + args[i] + "'";
-        throw error(logger, msg);
+        e.setDuring("implementing map");
+        throw e;
       }
     }
     println("cache.put(" + wrap(methodName) + ", args);");
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java
index 1754e94..6194e47 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsStringArrayMethodCreator.java
@@ -16,6 +16,7 @@
 package com.google.gwt.i18n.rebind;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 
@@ -60,7 +61,8 @@
   }
 
   @Override
-  public void createMethodFor(TreeLogger logger, JMethod method, String template) {
+  public void createMethodFor(TreeLogger logger, JMethod method, String key,
+      AbstractResource resource, String locale) throws UnableToCompleteException {
     String methodName = method.getName();
     // Make sure cache exists.
     enableCache();
@@ -68,6 +70,7 @@
     println("String args[] = (String[]) cache.get(" + wrap(methodName) + ");");
     // If not found, create String[].
     print("if (args == null){\n  String [] writer= {");
+    String template = resource.getRequiredStringExt(logger, key, null);
     String[] args = split(template);
     for (int i = 0; i < args.length; i++) {
       if (i != 0) {
diff --git a/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java b/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java
index 3455555..bb095fa 100644
--- a/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/ConstantsWithLookupImplCreator.java
@@ -23,7 +23,6 @@
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.TypeOracleException;
-import com.google.gwt.i18n.rebind.util.AbstractResource;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 import com.google.gwt.user.rebind.SourceWriter;
 
@@ -36,10 +35,23 @@
   private final Map<String, AbstractMethodCreator> namesToMethodCreators =
     new HashMap<String, AbstractMethodCreator>();
 
-  ConstantsWithLookupImplCreator(TreeLogger logger, SourceWriter writer,
+  /**
+   * Constructor for <code>ConstantsWithLookupImplCreator</code>.
+   * 
+   * @param logger logger to print errors
+   * @param deprecatedLogger logger to use for deprecated warnings
+   * @param writer <code>Writer</code> to print to
+   * @param localizableClass class/interface to conform to
+   * @param messageBindings resource bundle used to generate the class
+   * @param oracle types
+   * @throws UnableToCompleteException
+   */
+  ConstantsWithLookupImplCreator(TreeLogger logger,
+      TreeLogger deprecatedLogger, SourceWriter writer,
       JClassType localizableClass, AbstractResource messageBindings,
       TypeOracle oracle) throws UnableToCompleteException {
-    super(logger, writer, localizableClass, messageBindings, oracle);
+    super(logger, deprecatedLogger, writer, localizableClass, messageBindings,
+        oracle);
     try {
 
       // Boolean
@@ -139,19 +151,19 @@
    * arg0...argN.
    */
   @Override
-  protected void emitMethodBody(TreeLogger logger, JMethod method)
+  protected void emitMethodBody(TreeLogger logger, JMethod method, String locale)
       throws UnableToCompleteException {
     checkMethod(logger, method);
     if (method.getParameters().length == 1) {
       String name = method.getName();
       AbstractMethodCreator c = namesToMethodCreators.get(name);
       if (c != null) {
-        c.createMethodFor(logger, method, null);
+        c.createMethodFor(logger, method, name, null, locale);
         return;
       }
     }
     // fall through
-    super.emitMethodBody(logger, method);
+    super.emitMethodBody(logger, method, locale);
   }
 
   /**
diff --git a/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java b/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
index a83f17e..d71bffd 100644
--- a/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocaleInfoGenerator.java
@@ -46,21 +46,21 @@
    * their native locales (if possible).
    */
   private static final String GENERATED_LOCALE_NATIVE_DISPLAY_NAMES =
-      "com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-generated.properties";
+      "com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-generated.properties";
   
   /**
    * Properties file containing hand-made corrections to the machine-generated
    * locale display names above.
    */
   private static final String MANUAL_LOCALE_NATIVE_DISPLAY_NAMES =
-      "com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-manual.properties";
+      "com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-manual.properties";
 
   /**
    * Properties file containing hand-made overrides of locale display names,
    * in their native locales (if possible).
    */
   private static final String OVERRIDE_LOCALE_NATIVE_DISPLAY_NAMES =
-      "com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-override.properties";
+      "com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-override.properties";
   
   /**
    * The token representing the locale property controlling Localization.
diff --git a/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java b/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java
index cd5abb1..6107c6c 100644
--- a/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocalizableGenerator.java
@@ -27,7 +27,6 @@
 import com.google.gwt.i18n.client.Constants;
 import com.google.gwt.i18n.client.ConstantsWithLookup;
 import com.google.gwt.i18n.client.Messages;
-import com.google.gwt.i18n.rebind.util.ResourceFactory;
 
 /**
  * Generator used to bind classes extending the <code>Localizable</code> and
@@ -39,11 +38,12 @@
    */
   public static final String GWT_KEY = "gwt.key";
 
-  static final String CONSTANTS_NAME = Constants.class.getName();
+  public static final String CONSTANTS_NAME = Constants.class.getName();
 
-  static final String CONSTANTS_WITH_LOOKUP_NAME = ConstantsWithLookup.class.getName();
+  public static final String CONSTANTS_WITH_LOOKUP_NAME = ConstantsWithLookup.class.getName();
 
-  static final String MESSAGES_NAME = Messages.class.getName();
+  public static final String MESSAGES_NAME = Messages.class.getName();
+  
   private static long lastReloadCount = -1;
   /**
    * The token representing the locale property controlling Localization.
@@ -107,9 +107,12 @@
       throw new UnableToCompleteException();
     }
 
+    TreeLogger deprecatedLogger = logger.branch(TreeLogger.TRACE,
+        "Checking for deprecated metadata", null);
+    
     // Link current locale and interface type to correct implementation class.
     String generatedClass = AbstractLocalizableImplCreator.generateConstantOrMessageClass(
-        logger, context, locale, targetClass);
+        logger, deprecatedLogger , context, locale, targetClass);
     if (generatedClass != null) {
       return generatedClass;
     }
diff --git a/user/src/com/google/gwt/i18n/rebind/LocalizableLinkageCreator.java b/user/src/com/google/gwt/i18n/rebind/LocalizableLinkageCreator.java
index e467859..0e098c0 100644
--- a/user/src/com/google/gwt/i18n/rebind/LocalizableLinkageCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocalizableLinkageCreator.java
@@ -19,7 +19,6 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.user.rebind.AbstractSourceCreator;
-import com.google.gwt.i18n.rebind.util.ResourceFactory;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -28,7 +27,8 @@
  * Links classes with their localized counterparts.
  */
 class LocalizableLinkageCreator extends AbstractSourceCreator {
-  private static Map<String, JClassType> findDerivedClasses(TreeLogger logger,
+  
+  static Map<String, JClassType> findDerivedClasses(TreeLogger logger,
       JClassType baseClass) throws UnableToCompleteException {
     // Construct valid set of candidates for this type.
     Map<String, JClassType> matchingClasses = new HashMap<String, JClassType>();
@@ -74,7 +74,7 @@
               throw error(logger, dopClass.getQualifiedSourceName() + " and "
                   + subType.getQualifiedSourceName()
                   + " are both potential matches to " + baseClass
-                  + " in locale" + localeSubString);
+                  + " in locale " + localeSubString);
             }
             matchingClasses.put(localeSubString, subType);
           }
diff --git a/user/src/com/google/gwt/i18n/rebind/util/LocalizedPropertiesResource.java b/user/src/com/google/gwt/i18n/rebind/LocalizedPropertiesResource.java
similarity index 62%
rename from user/src/com/google/gwt/i18n/rebind/util/LocalizedPropertiesResource.java
rename to user/src/com/google/gwt/i18n/rebind/LocalizedPropertiesResource.java
index 2f8f955..8ccd20a 100644
--- a/user/src/com/google/gwt/i18n/rebind/util/LocalizedPropertiesResource.java
+++ b/user/src/com/google/gwt/i18n/rebind/LocalizedPropertiesResource.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.rebind.util;
+package com.google.gwt.i18n.rebind;
 
 import com.google.gwt.dev.util.Util;
 
@@ -54,7 +54,33 @@
 
   @Override
   public void addToKeySet(Set<String> s) {
-    s.addAll(props.getPropertyMap().keySet());
+    for (Object keyObj : props.getPropertyMap().keySet()) {
+      String key = (String) keyObj;
+      /*
+       * Remove plural forms from the key list.  They will be looked up as
+       * extensions to the base key (@see getStringExt()).
+       */
+      if (key.indexOf('[') < 0) {
+        s.add(key);
+      }
+    }
+  }
+
+  @Override
+  public String getStringExt(String key, String extension) {
+    if (extension != null) {
+      key += '[' + extension + ']';
+      /*
+       * Do not look up key extensions in other files, as they may not be
+       * relevant.  For example, if you are looking up the "one" French
+       * plural form and it isn't present, using the default fallback (which
+       * will typically be English) will result in an incorrect translation.
+       * Better to give a warning about a missing plural form and use the
+       * default rather than silently use an incorrect one.
+       */
+      return (String) getObjectAux(key, false);
+    }
+    return getString(key);
   }
 
   @Override
diff --git a/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
index 9e5b036..71322f5 100644
--- a/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/LookupMethodCreator.java
@@ -45,7 +45,7 @@
 
   @Override
   public void createMethodFor(TreeLogger logger, JMethod targetMethod,
-      String value) {
+      String key, AbstractResource resource, String locale) {
     createMethodFor(targetMethod);
   }
 
diff --git a/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java b/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java
index 3f91ea2..883df33 100644
--- a/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/MessagesImplCreator.java
@@ -21,7 +21,6 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.i18n.rebind.util.AbstractResource;
 import com.google.gwt.user.rebind.SourceWriter;
 
 /**
@@ -29,6 +28,7 @@
  * standard <code>AbstractGeneratorClassCreator</code>.
  */
 class MessagesImplCreator extends AbstractLocalizableImplCreator {
+  
   /**
    * Constructor for <code>ConstantsImplCreator</code>.
    * 
@@ -37,12 +37,13 @@
    * @param messageBindings resource bundle used to generate the class
    * @param oracle types
    * @param logger logger to print errors
+   * @param deprecatedLogger logger for deprecated metadata warnings
    * @throws UnableToCompleteException
    */
-  public MessagesImplCreator(TreeLogger logger, SourceWriter writer,
+  public MessagesImplCreator(TreeLogger logger, TreeLogger deprecatedLogger, SourceWriter writer,
       JClassType localizableClass, AbstractResource messageBindings,
       TypeOracle oracle) throws UnableToCompleteException {
-    super(writer, localizableClass, messageBindings);
+    super(logger, deprecatedLogger, writer, localizableClass, messageBindings, false);
     try {
       JClassType stringClass = oracle.getType(String.class.getName());
       register(stringClass, new MessagesMethodCreator(this));
@@ -77,9 +78,9 @@
    * @throws UnableToCompleteException
    */
   @Override
-  protected void emitMethodBody(TreeLogger logger, JMethod m)
+  protected void emitMethodBody(TreeLogger logger, JMethod m, String locale)
       throws UnableToCompleteException {
     checkMessagesMethod(logger, m);
-    delegateToCreator(logger, m);
+    delegateToCreator(logger, m, locale);
   }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/util/MessagesInterfaceCreator.java b/user/src/com/google/gwt/i18n/rebind/MessagesInterfaceCreator.java
similarity index 98%
rename from user/src/com/google/gwt/i18n/rebind/util/MessagesInterfaceCreator.java
rename to user/src/com/google/gwt/i18n/rebind/MessagesInterfaceCreator.java
index bef8b5d..1e56d03 100644
--- a/user/src/com/google/gwt/i18n/rebind/util/MessagesInterfaceCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/MessagesInterfaceCreator.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.rebind.util;
+package com.google.gwt.i18n.rebind;
 
 import com.google.gwt.i18n.client.Messages;
 
diff --git a/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
index 50f2059..06bd300 100644
--- a/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
@@ -17,77 +17,510 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.i18n.rebind.util.MessagesInterfaceCreator;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.i18n.client.PluralRule;
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+import com.google.gwt.i18n.client.Messages.Optional;
+import com.google.gwt.i18n.client.Messages.PluralCount;
+import com.google.gwt.i18n.client.Messages.PluralText;
+import com.google.gwt.i18n.client.impl.plurals.DefaultRule;
 import com.google.gwt.user.rebind.AbstractGeneratorClassCreator;
 import com.google.gwt.user.rebind.AbstractMethodCreator;
 
-import java.text.MessageFormat;
-import java.text.ParseException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Creator for methods of the form String getX(arg0,...,argN).
  */
 class MessagesMethodCreator extends AbstractMethodCreator {
+  
+  /**
+   * Helper class to produce string expressions consisting of literals
+   * and computed values.
+   */
+  private static class StringGenerator {
+
+    /**
+     * True if we are in the middle of a string literal
+     */
+    private boolean inString;
+    
+    /**
+     * True if we have produced any output
+     */
+    private boolean producedOutput;
+    
+    /**
+     * Output string buffer
+     */
+    private StringBuffer buf;
+
+    /**
+     * Initialize the StringGenerator with an output buffer.
+     * 
+     * @param buf output buffer
+     */
+    public StringGenerator(StringBuffer buf) {
+      this.buf = buf;
+      producedOutput = false;
+      inString = false;
+    }
+    
+    /**
+     * Append an expression to this string expression.
+     *  
+     * @param expression to add
+     */
+    public void appendExpression(String expression) {
+      appendExpression(expression, false);
+    }
+    
+    /**
+     * Append an expression to this string expression.
+     * 
+     * @param expression to add
+     * @param knownToBeString true if the expression is known to be of
+     *     string type; otherwise ""+ will be prepended to ensure it.
+     */
+    public void appendExpression(String expression, boolean knownToBeString) {
+      if (inString) {
+        buf.append('"');
+        inString = false;
+        producedOutput = true;
+      }
+      if (producedOutput) {
+        buf.append(" + ");
+      } else if (!knownToBeString) {
+        buf.append("\"\" + ");
+      }
+      buf.append(expression);
+      producedOutput = true;
+    }
+    
+    /**
+     * Append part of a string literal.
+     *  
+     * @param ch part of string literal
+     */
+    public void appendStringLiteral(char ch) {
+      if (!inString) {
+        if (producedOutput) {
+          buf.append(" + ");
+        } else {
+          producedOutput = true;
+        }
+        buf.append('"');
+        inString = true;
+      }
+      buf.append(ch);
+    }
+    
+    /**
+     * Append part of a string literal.
+     *  
+     * @param str part of string literal
+     */
+    public void appendStringLiteral(String str) {
+      if (!inString) {
+        if (producedOutput) {
+          buf.append(" + ");
+        } else {
+          producedOutput = true;
+        }
+        buf.append('"');
+        inString = true;
+      }
+      buf.append(str);
+    }
+    
+    /**
+     * Complete the string, closing an open quote and handling empty strings.
+     */
+    public void completeString() {
+      if (inString) {
+        buf.append('\"');
+      } else if (!producedOutput) {
+        buf.append("\"\"");
+      }
+    }
+  }
+
+  private interface ValueFormatter {
+    /**
+     * Creates code to format a value according to a format string.
+     * 
+     * @param out StringBuffer to append to
+     * @param subformat the remainder of the format string
+     * @param argName the name of the argument to use in the generated code
+     * @param argType the type of the argument
+     * @return null if no error or an appropriate error message
+     */
+    String format(StringGenerator out, String subformat, String argName,
+        JType argType);
+  }
+
+  /**
+   * Implements {x,date...} references in MessageFormat.
+   */
+  private static class DateFormatter implements ValueFormatter {
+    public String format(StringGenerator out, String subformat, String argName,
+        JType argType) {
+      if (!"java.util.Date".equals(argType.getQualifiedSourceName())) {
+        return "Only java.util.Date acceptable for date format";
+      }
+      if (subformat == null || "medium".equals(subformat)) {
+        out.appendExpression(dtFormatClassName + ".getMediumDateFormat()"
+            + ".format(" + argName + ")", true);
+      } else if ("full".equals(subformat)) {
+        out.appendExpression(dtFormatClassName + ".getFullDateFormat().format("
+            + argName + ")", true);
+      } else if ("long".equals(subformat)) {
+        out.appendExpression(dtFormatClassName + ".getLongDateFormat().format("
+            + argName + ")", true);
+      } else if ("short".equals(subformat)) {
+        out.appendExpression(dtFormatClassName + ".getShortDateFormat()"
+            + ".format(" + argName + ")", true);
+      } else {
+        out.appendExpression(dtFormatClassName + ".getFormat("
+            + wrap(subformat) + ").format(" + argName + ")", true);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Implements {x,number...} references in MessageFormat.
+   */
+  private static class NumberFormatter implements ValueFormatter {
+    public String format(StringGenerator out, String subformat, String argName,
+        JType argType) {
+      JPrimitiveType argPrimType = argType.isPrimitive();
+      if (argPrimType == null || argPrimType == JPrimitiveType.BOOLEAN
+          || argPrimType == JPrimitiveType.VOID) {
+        return "Illegal argument type for number format";
+      }
+      if (subformat == null) {
+        out.appendExpression(numFormatClassName + ".getDecimalFormat().format("
+            + argName + ")", true);
+      } else if ("integer".equals(subformat)) {
+        out.appendExpression(numFormatClassName + ".getIntegerFormat().format("
+            + argName + ")", true);
+      } else if ("currency".equals(subformat)) {
+        out.appendExpression(numFormatClassName + ".getCurrencyFormat().format("
+            + argName + ")", true);
+      } else if ("percent".equals(subformat)) {
+        out.appendExpression(numFormatClassName + ".getPercentFormat().format("
+            + argName + ")", true);
+      } else {
+        out.appendExpression(numFormatClassName + ".getFormat(" + wrap(subformat)
+            + ").format(" + argName + ")", true);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Implements {x,time...} references in MessageFormat.
+   */
+  private static class TimeFormatter implements ValueFormatter {
+    public String format(StringGenerator out, String subformat, String argName,
+        JType argType) {
+      if (!"java.util.Date".equals(argType.getQualifiedSourceName())) {
+        return "Only java.util.Date acceptable for date format";
+      }
+      if (subformat == null || "medium".equals(subformat)) {
+        out.appendExpression(dtFormatClassName
+            + ".getMediumTimeFormat().format(" + argName + ")", true);
+      } else if ("full".equals(subformat)) {
+        out.appendExpression(dtFormatClassName + ".getFullTimeFormat().format("
+            + argName + ")", true);
+      } else if ("long".equals(subformat)) {
+        out.appendExpression(dtFormatClassName + ".getLongTimeFormat().format("
+            + argName + ")", true);
+      } else if ("short".equals(subformat)) {
+        out.appendExpression(dtFormatClassName
+            + ".getShortTimeFormat().format(" + argName + ")", true);
+      } else {
+        out.appendExpression(dtFormatClassName + ".getFormat("
+            + wrap(subformat) + ").format(" + argName + ")", true);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Class names, in a refactor-friendly manner.
+   */
+  private static final String dtFormatClassName = DateTimeFormat.class.getCanonicalName();
+  private static final String numFormatClassName = NumberFormat.class.getCanonicalName();
+  
+  /**
+   * Pattern to find MessageFormat argument references, including format
+   * and subformat pieces, if present.
+   */
+  private static final Pattern argPattern
+      = Pattern.compile("\\{(\\d+)(,(\\w+)(,([^\\}]+))?)?\\}");
+
+  /**
+   * Map of supported formats.
+   */
+  private static Map<String, ValueFormatter> formatters = new HashMap<String, ValueFormatter>();
+  
+  /*
+   * Register supported formats. 
+   */
+  static {
+    formatters.put("date", new DateFormatter());
+    formatters.put("number", new NumberFormatter());
+    formatters.put("time", new TimeFormatter());
+    // TODO: implement ChoiceFormat and PluralFormat
+  }
+
   /**
    * Constructor for <code>MessagesMethodCreator</code>.
    * 
    * @param classCreator associated class creator
+   * @param localizableClass class we are generating methods for
    */
   public MessagesMethodCreator(AbstractGeneratorClassCreator classCreator) {
     super(classCreator);
   }
 
   @Override
-  public void createMethodFor(TreeLogger logger, JMethod m, String template)
-      throws UnableToCompleteException {
-    int numParams = m.getParameters().length;
+  public void createMethodFor(TreeLogger logger, JMethod m, String key,
+      AbstractResource resource, String locale) throws UnableToCompleteException {
+    JParameter[] params = m.getParameters();
+    int pluralParamIndex = -1;
+    Class<? extends PluralRule> ruleClass = null;
+    int numParams = params.length;
+    boolean[] seenFlags = new boolean[numParams];
 
-    // Compile time checks of the message
-    Object[] expected;
-
-    // Find safe string to store 'real' quotes during escape.
-    // Using '~' rather than null string or one of a-X because we can
-    // easily test what happens with multiple '~'s.
-    String safeReplaceString = "~";
-
-    while (template.indexOf(safeReplaceString) >= 0) {
-      safeReplaceString += "~";
+    // See if any parameter is tagged as a PluralCount parameter.
+    for (int i = 0; i < numParams; ++i) {
+      PluralCount pluralCount = params[i].getAnnotation(PluralCount.class);
+      if (pluralCount != null) {
+        if (pluralParamIndex >= 0) {
+          throw error(logger, m.getName() + ": there can only be one PluralCount parameter");
+        }
+        JPrimitiveType primType = params[i].getType().isPrimitive();
+        if (primType != JPrimitiveType.INT && primType != JPrimitiveType.SHORT) {
+          throw error(logger, m.getName() + ": PluralCount parameter must be int or short");
+        }
+        pluralParamIndex = i;
+        ruleClass = pluralCount.value();
+      }
+    }
+    
+    StringBuffer generated = new StringBuffer();
+    if (ruleClass == null) {
+      if (m.getAnnotation(PluralText.class) != null) {
+        logger.log(TreeLogger.WARN, "Unused @PluralText on "
+            + m.getEnclosingType().getSimpleSourceName() + "." + m.getName()
+            + "; did you intend to mark a @PluralCount parameter?", null);
+      }
+    } else {
+      if (ruleClass == PluralRule.class) {
+        ruleClass = DefaultRule.class;
+      }
+      PluralRule rule = createLocalizedPluralRule(logger, m.getEnclosingType().getOracle(),
+          ruleClass, locale);
+      logger.log(TreeLogger.TRACE, "Using plural rule " + rule.getClass() + " for locale '"
+          + locale + "'", null);
+      generated.append(PluralRule.class.getCanonicalName());
+      generated.append(" rule = new " + rule.getClass().getCanonicalName() + "();\n");
+      generated.append("switch (rule.select(arg" + pluralParamIndex + ")) {\n");
+      PluralForm[] pluralForms = rule.pluralForms();
+      resource.setPluralForms(key, pluralForms);
+      // Skip default plural form (index 0); the fall-through case will handle
+      // it.
+      for (int i = 1; i < pluralForms.length; ++i) {
+        String template = resource.getStringExt(key, pluralForms[i].getName());
+        if (template != null) {
+          generated.append("  case " + i + ": return ");
+          generateString(logger, template, params, seenFlags, generated);
+          generated.append(";\n");
+        } else if (pluralForms[i].getWarnIfMissing()) {
+          logger.log(TreeLogger.WARN, "No plural form '" + pluralForms[i].getName()
+              + "' defined for method '" + m.getName() + "' in " + m.getEnclosingType()
+              + " for locale " + locale, null);
+        }
+      }
+      generated.append("}\n");
+    }
+    generated.append("return ");
+    String template = resource.getRequiredStringExt(logger, key, null);
+    generateString(logger, template, params, seenFlags, generated);
+    
+    // Generate an error if any required parameter was not used somewhere.
+    for (int i = 0; i < numParams; ++i) {
+      if (!seenFlags[i]) {
+        Optional optional = params[i].getAnnotation(Optional.class);
+        if (optional == null) {
+          throw error(logger, "Required argument " + i + " not present: "
+              + template);
+        }
+      }
     }
 
-    try {
-      int numArgs = MessagesInterfaceCreator.numberOfMessageArgs(template);
-      expected = new Object[numArgs];
-    } catch (ParseException e) {
-      logger.log(TreeLogger.INFO, "Failed to parse the message " + template
-          + " so cannot verify the number of passed-in arguments", e);
-      expected = new Object[numParams];
-    }
+    generated.append(';');
+    println(generated.toString());
+  }
 
-    if (numParams != expected.length) {
-      StringBuffer msg = new StringBuffer();
-      msg.append("The method has ");
-      msg.append(numParams);
-      msg.append(numParams == 1 ? " parameter" : " parameters");
-      msg.append(", but the message template has ");
-      msg.append(expected.length);
-      msg.append(expected.length == 1 ? " placeholder" : " placeholders");
-      throw error(logger, msg.toString());
+  /**
+   * Creates an instance of a locale-specific plural rule implementation.
+   * 
+   * Note that this uses TypeOracle's ability to find all subclasses of the
+   * supplied parent class, then uses reflection to actually load the class.
+   * This works because PluralRule instances are required to be translatable,
+   * since part of them is executed at runtime and part at compile time.
+   * 
+   * @param logger TreeLogger instance
+   * @param oracle TypeOracle instance to use
+   * @param ruleClass PluralRule implementation to localize
+   * @param locale current locale we are compiling for
+   * @return an instance of a PluralRule implementation.  If an appropriate implementation
+   *     of the requested class cannot be found, an instance of DefaultRule is used instead
+   *     as a default of last resort.
+   * @throws UnableToCompleteException if findDerivedClasses fails
+   * 
+   * TODO: consider impact of possibly having multiple TypeOracles
+   */
+  private PluralRule createLocalizedPluralRule(TreeLogger logger, TypeOracle oracle,
+      Class<? extends PluralRule> ruleClass, String locale) throws UnableToCompleteException {
+    if (locale.length() == 0) {
+      locale = ResourceFactory.DEFAULT_TOKEN;
     }
-    for (int i = 0; i < expected.length; i++) {
-      expected[i] = safeReplaceString + " + arg" + i + " + "
-          + safeReplaceString;
+    String baseName = ruleClass.getCanonicalName();
+    JClassType ruleJClassType = oracle.findType(baseName);
+    Map<String, JClassType> matchingClasses
+        = LocalizableLinkageCreator.findDerivedClasses(logger, ruleJClassType);
+    while (true) {
+      JClassType localizedType = matchingClasses.get(locale);
+      if (localizedType != null) {
+        try {
+          Class<?> testClass = Class.forName(localizedType.getQualifiedSourceName());
+          if (PluralRule.class.isAssignableFrom(testClass)) {
+            return (PluralRule) testClass.newInstance();
+          }
+        } catch (ClassCastException e) {
+          // ignore classes of the wrong type
+        } catch (ClassNotFoundException e) {
+          // ignore missing classes
+        } catch (InstantiationException e) {
+          // skip classes we can't instantiate
+        } catch (IllegalAccessException e) {
+          // ignore inaccessible classes
+        }
+      }
+      if (locale.equals(ResourceFactory.DEFAULT_TOKEN)) {
+        // default of last resort
+        return new DefaultRule();
+      }
+      locale = ResourceFactory.getParentLocaleName(locale);
     }
-    String formattedString;
-    try {
-      formattedString = MessageFormat.format(template, expected);
-    } catch (IllegalArgumentException e) {
-      throw error(logger, "Message Template '" + template
-          + "' did not format correctly", e);
+  }
+
+  /**
+   * Generate a Java string for a given MessageFormat string.
+   *
+   * @param logger
+   * @param template
+   * @param params
+   * @param seenFlag
+   * @param outputBuf
+   * @throws UnableToCompleteException
+   */
+  private void generateString(TreeLogger logger, String template, JParameter[] params,
+      boolean[] seenFlag, StringBuffer outputBuf) throws UnableToCompleteException {
+    StringGenerator buf = new StringGenerator(outputBuf);
+    Matcher match = argPattern.matcher(template);
+    int curPos = 0;
+    boolean inQuote = false;
+    int templateLen = template.length();
+    while (curPos < templateLen) {
+      char ch = template.charAt(curPos++);
+      switch (ch) {
+        case '\'':
+          if (curPos < templateLen && template.charAt(curPos) == '\'') {
+            buf.appendStringLiteral(ch);
+            ++curPos;
+            break;
+          }
+          inQuote = !inQuote;
+          break;
+
+        case '{':
+          if (!inQuote) {
+            if (match.find(curPos - 1) && match.start() == curPos - 1) {
+              // match group 1 is the argument number (zero-based)
+              // match group 3 is the format name or null if none
+              // match group 5 is the subformat string or null if none
+              int argNumber = Integer.valueOf(match.group(1));
+              if (argNumber >= params.length) {
+                throw error(logger, "Argument " + argNumber + " beyond range of arguments: "
+                    + template);
+              }
+              seenFlag[argNumber] = true;
+              String arg = "arg" + match.group(1);
+              curPos = match.end();
+              String format = match.group(3);
+              if (format != null) {
+                String subformat = match.group(5);
+                ValueFormatter formatter = formatters.get(format);
+                if (formatter != null) {
+                  String err = formatter.format(buf, subformat, arg, params[argNumber].getType());
+                  if (err != null) {
+                    throw error(logger, err);
+                  }
+                  continue;
+                }
+              }
+              // no format specified or unknown format
+              // have to ensure that the result is stringified if necessary
+              buf.appendExpression(arg,
+                  "java.lang.String".equals(params[argNumber].getType().getQualifiedSourceName()));
+            } else {
+              throw error(logger,
+                  "Invalid message format - { not start of valid argument" + template);
+            }
+            break;
+          }
+          buf.appendStringLiteral(ch);
+          break;
+
+        case '\n':
+          buf.appendStringLiteral("\\n");
+          break;
+
+        case '\r':
+          buf.appendStringLiteral("\\r");
+          break;
+
+        case '\\':
+        case '"':
+          buf.appendStringLiteral('\\');
+          // CHECKSTYLE_OFF
+          // fall-through
+
+        default:
+          // CHECKSTYLE_ON
+          buf.appendStringLiteral(ch);
+          break;
+      }
     }
-    formattedString = wrap(formattedString);
-    formattedString = formattedString.replaceAll(safeReplaceString, "\"");
-    String templateToSplit = "return " + formattedString + ";";
-    println(templateToSplit);
+    if (inQuote) {
+      throw error(logger, "Unterminated single quote: " + template);
+    }
+    buf.completeString();
   }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java b/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
new file mode 100644
index 0000000..1905946
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/ResourceFactory.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright 2008 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.rebind;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.i18n.rebind.AnnotationsResource.AnnotationsError;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Set;
+
+/**
+ * Creates resources.
+ */
+public abstract class ResourceFactory {
+  static class SimplePathTree extends AbstractPathTree {
+    String path;
+
+    SimplePathTree(String path) {
+      this.path = path;
+    }
+
+    @Override
+    public AbstractPathTree getChild(int i) {
+      throw new UnsupportedOperationException(
+          "Simple paths have no children, therefore cannot get child: " + i);
+    }
+
+    @Override
+    public String getPath() {
+      return path;
+    }
+
+    @Override
+    public int numChildren() {
+      return 0;
+    }
+  }
+
+  private abstract static class AbstractPathTree {
+    abstract AbstractPathTree getChild(int i);
+    
+    JClassType getJClassType(JClassType clazz) {
+      return clazz;
+    }
+
+    abstract String getPath();
+
+    abstract int numChildren();
+  }
+
+  private static class ClassPathTree extends AbstractPathTree {
+    Class<?> javaInterface;
+
+    ClassPathTree(Class<?> javaInterface) {
+      this.javaInterface = javaInterface;
+    }
+
+    @Override
+    AbstractPathTree getChild(int i) {
+      // we expect to do this at most once, so no caching is used.
+      return new ClassPathTree(javaInterface.getInterfaces()[i]);
+    }
+
+    @Override
+    String getPath() {
+      return javaInterface.getName();
+    }
+
+    @Override
+    int numChildren() {
+      return javaInterface.getInterfaces().length;
+    }
+  }
+
+  private static class JClassTypePathTree extends AbstractPathTree {
+    JClassType javaInterface;
+
+    JClassTypePathTree(JClassType javaInterface) {
+      this.javaInterface = javaInterface;
+    }
+
+    @Override
+    AbstractPathTree getChild(int i) {
+      // we expect to do this at most once, so no caching is used.
+      return new JClassTypePathTree(javaInterface.getImplementedInterfaces()[i]);
+    }
+
+    @Override
+    JClassType getJClassType(JClassType clazz) {
+      return javaInterface;
+    }
+    
+    /**
+     * Path is equivalent to javaInterface.getQualifiedName() except for inner
+     * classes.
+     * 
+     * @see com.google.gwt.i18n.rebind.ResourceFactory.AbstractPathTree#getPath()
+     */
+    @Override
+    String getPath() {
+      String name = getResourceName(javaInterface);
+      String packageName = javaInterface.getPackage().getName();
+      return packageName + "." + name;
+    }
+
+    @Override
+    int numChildren() {
+      return javaInterface.getImplementedInterfaces().length;
+    }
+  }
+
+  /**
+   * Represents default locale.
+   */
+  public static final String DEFAULT_TOKEN = "default";
+  public static final char LOCALE_SEPARATOR = '_';
+
+  public static final AbstractResource NOT_FOUND = new AbstractResource() {
+
+    @Override
+    void addToKeySet(Set<String> s) {
+      throw new IllegalStateException("Not found resource");
+    }
+
+    @Override
+    Object handleGetObject(String key) {
+      throw new IllegalStateException("Not found resource");
+    }
+  };
+
+  private static Map<String, AbstractResource> cache = new HashMap<String, AbstractResource>();
+
+  private static List<ResourceFactory> loaders = new ArrayList<ResourceFactory>();
+  static {
+    loaders.add(new LocalizedPropertiesResource.Factory());
+  }
+
+  /**
+   * Clears the resource cache.
+   */
+  public static void clearCache() {
+    cache.clear();
+  }
+
+  public static AbstractResource getAnnotations(TreeLogger logger, JClassType targetClass,
+      String locale, boolean isConstants) throws UnableToCompleteException {
+    Map<String, JClassType> matchingClasses
+        = LocalizableLinkageCreator.findDerivedClasses(logger, targetClass);
+    matchingClasses.put(ResourceFactory.DEFAULT_TOKEN, targetClass);
+    String localeSuffix = locale;
+    JClassType currentClass = null;
+    AnnotationsResource previous = null;
+    AbstractResource result = null;
+    while (true) {
+      currentClass = matchingClasses.get(localeSuffix);
+      if (currentClass != null) {
+        AnnotationsResource resource;
+        try {
+          resource = new AnnotationsResource(logger, currentClass, isConstants);
+        } catch (AnnotationsError e) {
+          logger.log(TreeLogger.ERROR, e.getMessage(), e);
+          throw new UnableToCompleteException();
+        }
+        if (resource.notEmpty()) {
+          if (result == null) {
+            result = resource;
+          }
+          if (previous != null) {
+            previous.setParentResource(resource);
+          }
+          previous = resource;
+        }
+      }
+      if (localeSuffix.equals(ResourceFactory.DEFAULT_TOKEN)) {
+        return result;
+      }
+      
+      localeSuffix = ResourceFactory.getParentLocaleName(localeSuffix);
+    }
+  }
+
+  public static AbstractResource getBundle(Class<?> clazz, String locale, boolean isConstants) {
+    return getBundle(createNullTreeLogger(), clazz, locale, isConstants);
+  }
+
+  public static AbstractResource getBundle(String path, String locale, boolean isConstants) {
+    return getBundle(createNullTreeLogger(), path, locale, isConstants);
+  }
+
+  /**
+   * Gets the resource associated with the given interface.
+   * 
+   * @param javaInterface interface
+   * @param locale locale name
+   * @return the resource
+   */
+  public static AbstractResource getBundle(TreeLogger logger, Class<?> javaInterface,
+      String locale, boolean isConstants) {
+    if (javaInterface.isInterface() == false) {
+      throw new IllegalArgumentException(javaInterface
+          + " should be an interface.");
+    }
+    ClassPathTree path = new ClassPathTree(javaInterface);
+    return getBundleAux(logger, path, null, locale, true, isConstants);
+  }
+
+  /**
+   * Gets the resource associated with the given interface.
+   * 
+   * @param javaInterface interface
+   * @param locale locale name
+   * @return the resource
+   */
+  public static AbstractResource getBundle(TreeLogger logger, JClassType javaInterface,
+      String locale, boolean isConstants) {
+    return getBundleAux(logger, new JClassTypePathTree(javaInterface), javaInterface, locale,
+        true, isConstants);
+  }
+
+  /**
+   * Gets the resource associated with the given path.
+   * 
+   * @param path the path
+   * @param locale locale name
+   * @return the resource
+   */
+  public static AbstractResource getBundle(TreeLogger logger, String path, String locale,
+      boolean isConstants) {
+    return getBundleAux(logger, new SimplePathTree(path), null, locale, true, isConstants);
+  }
+
+  /**
+   * Given a locale name, derives the parent's locale name. For example, if
+   * the locale name is "en_US", the parent locale name would be "en". If the
+   * locale name is that of a top level locale (i.e. no '_' characters, such
+   * as "fr"), then the the parent locale name is that of the default locale.
+   * If the locale name is null, the empty string, or is already that of the
+   * default locale, then null is returned.
+   *
+   * @param localeName the locale name
+   * @return the parent's locale name
+   */
+  public static String getParentLocaleName(String localeName) {
+    if (localeName == null ||
+        localeName.length() == 0 ||
+        localeName.equals(DEFAULT_TOKEN)) {
+      return null;
+    }
+    int pos = localeName.lastIndexOf(LOCALE_SEPARATOR);
+    if (pos != -1) {
+      return localeName.substring(0, pos);
+    }
+    return DEFAULT_TOKEN;
+  }
+
+  public static String getResourceName(JClassType targetClass) {
+    String name = targetClass.getName();
+    if (targetClass.isMemberType()) {
+      name = name.replace('.', '$');
+    }
+    return name;
+  }
+
+  private static TreeLogger createNullTreeLogger() {
+    return new TreeLogger() {
+      public TreeLogger branch(Type type, String msg, Throwable caught) {
+        return null;
+      }
+
+      public boolean isLoggable(Type type) {
+        return false;
+      }
+
+      public void log(Type type, String msg, Throwable caught) {
+      }
+    };
+  }
+
+  private static List<AbstractResource> findAlternativeParents(TreeLogger logger,
+      ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
+      boolean isConstants) {
+    List<AbstractResource> altParents = null;
+    if (tree != null) {
+      altParents = new ArrayList<AbstractResource>();
+      for (int i = 0; i < tree.numChildren(); i++) {
+        ResourceFactory.AbstractPathTree child = tree.getChild(i);
+        AbstractResource altParent = getBundleAux(logger, child, child.getJClassType(clazz),
+            locale, false, isConstants);
+        if (altParent != null) {
+          altParents.add(altParent);
+        }
+      }
+    }
+    return altParents;
+  }
+
+  private static AbstractResource findPrimaryParent(TreeLogger logger,
+      ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale,
+      boolean isConstants) {
+
+    // If we are not in the default case, calculate parent
+    if (!DEFAULT_TOKEN.equals(locale)) {
+      return getBundleAux(logger, tree, clazz, getParentLocaleName(locale), false, isConstants);
+    }
+    return null;
+  }
+
+  private static AbstractResource getBundleAux(TreeLogger logger,
+      ResourceFactory.AbstractPathTree tree, JClassType clazz, String locale, boolean required,
+      boolean isConstants) {
+    String targetPath = tree.getPath();
+    ClassLoader loader = AbstractResource.class.getClassLoader();
+    Map<String, JClassType> matchingClasses = null;
+    if (clazz != null) {
+      try {
+        matchingClasses = LocalizableLinkageCreator.findDerivedClasses(logger, clazz);
+        /* 
+         * In this case, we specifically want to be able to look at the interface
+         * instead of just implementations.
+         */
+        matchingClasses.put(ResourceFactory.DEFAULT_TOKEN, clazz);
+      } catch (UnableToCompleteException e) {
+        // ignore error, fall through
+      }
+    }
+    if (matchingClasses == null) {
+      // empty map
+      matchingClasses = new HashMap<String, JClassType>();
+    }
+
+    if (locale == null || locale.length() == 0) {
+      // This should never happen, since the only legitimate user of this
+      // method traces back to AbstractLocalizableImplCreator. The locale
+      // that is passed in from AbstractLocalizableImplCreator is produced
+      // by the I18N property provider, which guarantees that the locale
+      // will not be of zero length or null. However, we add this check
+      // in here in the event that a future user of ResourceFactory does
+      // not obey this constraint.
+      locale = DEFAULT_TOKEN;
+    }
+
+    // Calculate baseName
+    String localizedPath = targetPath;
+    if (!DEFAULT_TOKEN.equals(locale)) {
+      localizedPath = targetPath + LOCALE_SEPARATOR + locale;
+    }
+    AbstractResource result = cache.get(localizedPath);
+    if (result != null) {
+      if (result == NOT_FOUND) {
+        return null;
+      } else {
+        return result;
+      }
+    }
+    String partialPath = localizedPath.replace('.', '/');
+    AbstractResource parent = findPrimaryParent(logger, tree, clazz, locale, isConstants);
+    List<AbstractResource> altParents = findAlternativeParents(logger, tree, clazz, locale, isConstants);
+
+    AbstractResource found = null;
+    JClassType currentClass = matchingClasses.get(locale);
+    if (currentClass != null) {
+      AnnotationsResource resource;
+      try {
+        resource = new AnnotationsResource(logger, currentClass, isConstants);
+        if (resource.notEmpty()) {
+          found = resource;
+          found.setPath(currentClass.getQualifiedSourceName());
+        }
+      } catch (AnnotationsError e) {
+        logger.log(TreeLogger.ERROR, e.getMessage(), e);
+      }
+    }
+    for (int i = 0; found == null && i < loaders.size(); i++) {
+      ResourceFactory element = loaders.get(i);
+      String path = partialPath + "." + element.getExt();
+      InputStream m = loader.getResourceAsStream(path);
+      if (m != null) {
+        found = element.load(m);
+        found.setPath(path);
+      }
+    }
+    if (found == null) {
+      if (parent != null) {
+        found = parent;
+      } else {
+        found = NOT_FOUND;
+      }
+    } else {
+      found.setPrimaryParent(parent);
+      found.setLocaleName(locale);
+      for (int j = 0; j < altParents.size(); j++) {
+        AbstractResource altParent = altParents.get(j);
+        found.addAlternativeParent(altParent);
+      }
+      found.checkKeys();
+    }
+
+    cache.put(localizedPath, found);
+
+    if (found == NOT_FOUND) {
+      if (required) {
+        throw new MissingResourceException(
+          "Could not find any resource associated with " + tree.getPath(),
+            null, null);
+      } else {
+        return null;
+      }
+    }
+
+    // At this point, found cannot be equal to null or NOT_FOUND
+    return found;
+  }
+
+  abstract String getExt();
+
+  abstract AbstractResource load(InputStream m);
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java b/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java
index 1bb3f4d..00db1d4 100644
--- a/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java
+++ b/user/src/com/google/gwt/i18n/rebind/SimpleValueMethodCreator.java
@@ -88,7 +88,8 @@
 
   @Override
   public void createMethodFor(TreeLogger logger, JMethod targetMethod,
-      String value) throws UnableToCompleteException {
+      String key, AbstractResource resource, String locale) throws UnableToCompleteException {
+    String value = resource.getRequiredStringExt(logger, key, null);
     try {
       String translatedValue = valueCreator.getValue(value);
       println("return " + translatedValue + ";");
diff --git a/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java b/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
new file mode 100644
index 0000000..a3ff4b2
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/format/MessageCatalogFormat.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 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.rebind.format;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.i18n.rebind.AbstractResource;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for writing various message catalog formats.
+ * 
+ * <p><hr><b>WARNING:</b> this API is expected to change as we develop additional
+ * message catalog formats.  In particular, this interface will be extended
+ * to support reading message catalogs and further changes may be required.
+ * <hr></p>
+ * 
+ * <p>Implementations of this interface are executed at compile time and
+ * therefore must not contain any JSNI code.
+ * </p>
+ */
+public interface MessageCatalogFormat {
+  
+  /**
+   * Write a message catalog file.
+   * 
+   * @param logger TreeLogger for logging errors/etc
+   * @param resource the contents to write
+   * @param out the PrintWriter to generate output on
+   * @param messageInterface the interface to create (so additional
+   *     annotations may be accessed)
+   */
+  void write(TreeLogger logger, AbstractResource resource, PrintWriter out,
+      JClassType messageInterface);
+  
+  /**
+   * @return the extension to use for this file type, including the dot
+   */
+  String getExtension();
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/format/PropertiesFormat.java b/user/src/com/google/gwt/i18n/rebind/format/PropertiesFormat.java
new file mode 100644
index 0000000..88add3c
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/format/PropertiesFormat.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2008 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.rebind.format;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.i18n.client.PluralRule.PluralForm;
+import com.google.gwt.i18n.rebind.AbstractResource;
+import com.google.gwt.i18n.rebind.AnnotationsResource;
+import com.google.gwt.i18n.rebind.AnnotationsResource.ArgumentInfo;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * Writes GWT-style Java properties files for translation.  This catalog
+ * format does not support aggregation of messages from multiple interfaces
+ * since there is no way to distinguish messages from another interface from
+ * those that were from this interface but no longer used.  The output file
+ * is assumed to be in UTF-8 encoding rather than using the {@code \\uXXXX}
+ * escapes.
+ */
+public class PropertiesFormat implements MessageCatalogFormat {
+
+  public String getExtension() {
+    return ".properties";
+  }
+
+  /*
+   * (non-Javadoc)
+   * 
+   * @see com.google.gwt.i18n.rebind.format.MessageCatalogFormat#write(com.google.gwt.i18n.rebind.util.AbstractResource,
+   *      java.io.File, com.google.gwt.core.ext.typeinfo.JClassType)
+   */
+  public void write(TreeLogger logger, AbstractResource resource, PrintWriter out,
+      JClassType messageInterface) {
+    AnnotationsResource annotResource = resource.getAnnotationsResource();
+    writeComment(out, "Generated from "
+        + messageInterface.getQualifiedSourceName());
+    String localeName = resource.getLocaleName();
+    if (localeName != null) {
+      writeComment(out, "for locale " + localeName);
+    }
+    // Sort keys for deterministic output.
+    Set<String> keySet = resource.keySet();
+    String[] sortedKeys = keySet.toArray(new String[keySet.size()]);
+    Arrays.sort(sortedKeys);
+    for (String key : sortedKeys) {
+      out.println();
+      if (annotResource != null) {
+        // Write comments from the annotations.
+        writeAnnotComments(out, annotResource, key);
+      }
+      
+      // Collect plural forms for this locale.
+      PluralForm[] pluralForms = resource.getPluralForms(key);
+      if (pluralForms != null) {
+        for (PluralForm form : pluralForms) {
+          String name = form.getName();
+          if ("other".equals(name)) {
+            // write the "other" description here, and the default message
+            writeComment(out, "- " + form.getDescription());
+            write(out, key, resource.getString(key));
+          } else {
+            String comment = "- plural form '" + form.getName() + "': " + form.getDescription();
+            if (!form.getWarnIfMissing()) {
+              comment += " (optional)";
+            }
+            writeComment(out, comment);
+            String translated = resource.getStringExt(key, form.getName());
+            if (translated == null) {
+              translated = "";
+            }
+            write(out, key + "[" + form.getName() + "]", translated);
+          }
+        }
+      } else {
+        write(out, key, resource.getString(key));
+      }
+    }
+  }
+
+  /**
+   * 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);
+  }
+  
+  /**
+   * Write a key-value pair to a properties file with proper quoting.
+   * 
+   * @param out PrintWriter to output to
+   * @param key property key
+   * @param value property value
+   */
+  private void write(PrintWriter out, String key, String value) {
+    out.print(quoteKey(key));
+    out.print('=');
+    out.println(quoteValue(value));
+  }
+  
+  /**
+   * Write comments before a line, pulled from the annotations on a
+   * given method.
+   * 
+   * @param out PrintWriter stream to write to
+   * @param annotResource AnnotationsResource to get annotation data from
+   * @param key key of method for lookup in annotResource
+   */
+  private void writeAnnotComments(PrintWriter out, AnnotationsResource annotResource, String key) {
+    String desc = annotResource.getDescription(key);
+    if (desc != null) {
+      writeComment(out, "Description: " + desc);
+    }
+    String meaning = annotResource.getMeaning(key);
+    if (meaning != null) {
+      writeComment(out, "Meaning: " + meaning);
+    }
+    Iterable<ArgumentInfo> arguments = annotResource.argumentsIterator(key);
+    StringBuffer buf = new StringBuffer();
+    if (arguments != null) {
+      int i = 0;
+      for (ArgumentInfo argInfo : arguments) {
+        if (i > 0) {
+          buf.append(", ");
+        }
+        buf.append(i++ + "=");
+        buf.append(argInfo.name);
+        boolean inParen = false;
+        if (argInfo.optional) {
+          buf.append(" (Optional");
+          inParen = true;
+        }
+        if (argInfo.isPluralCount) {
+          if (inParen) {
+            buf.append("; ");
+          } else {
+            buf.append(" (");
+            inParen = true;
+          }
+          buf.append("Plural Count");
+        }
+        if (argInfo.example != null) {
+          if (inParen) {
+            buf.append("; ");
+          } else {
+            buf.append(" (");
+            inParen = true;
+          }
+          buf.append("Example: " + argInfo.example);
+        }
+        if (inParen) {
+          buf.append(')');
+        }
+      }
+      if (i > 0) {
+        writeComment(out, buf.toString());
+      }
+    }
+  }
+
+  /**
+   * Write a comment to a properties file.
+   * 
+   * @param out PrintWriter to output to
+   * @param comment comment to write
+   */
+  private void writeComment(PrintWriter out, String comment) {
+    out.println("# " + comment);
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/rebind/format/package-info.java
similarity index 72%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/rebind/format/package-info.java
index eaca740..35321d1 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/rebind/format/package-info.java
@@ -13,16 +13,9 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
-
-import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
- * Placeholder for generated file.
+ * Implementations for various MessageCatalogFormat implementations, which are used
+ * to generate files for translation or to read translated messages.
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
-}
+package com.google.gwt.i18n.rebind.format;
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/rebind/keygen/FullyQualifiedMethodNameKeyGenerator.java
similarity index 65%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/rebind/keygen/FullyQualifiedMethodNameKeyGenerator.java
index eaca740..a835522 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/rebind/keygen/FullyQualifiedMethodNameKeyGenerator.java
@@ -13,16 +13,15 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
+package com.google.gwt.i18n.rebind.keygen;
 
-import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
- * Placeholder for generated file.
+ * Key generator using the fully-qualified method name.
  */
-public class CldrImpl_he extends CldrImpl {
+public class FullyQualifiedMethodNameKeyGenerator implements KeyGenerator {
 
-  public boolean isRTL() {
-    return true;
+  public String generateKey(String className, String methodName, String text, String meaning) {
+    return className + "." + methodName;
   }
 }
diff --git a/user/src/com/google/gwt/i18n/rebind/keygen/KeyGenerator.java b/user/src/com/google/gwt/i18n/rebind/keygen/KeyGenerator.java
new file mode 100644
index 0000000..6dcade8
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/keygen/KeyGenerator.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 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.rebind.keygen;
+
+/**
+ * Interface for key generation algorithms used by <code>LocalizableResource</code>
+ * implementations.
+ * 
+ * Implementations of this interface are executed at compile time, and
+ * therefore must not contain any JSNI code.
+ */
+public interface KeyGenerator {
+
+  /**
+   * Generates a key for a given method with its default text and meaning.
+   * 
+   * @param className fully qualified name of the Messages/Constants subinterface
+   * @param methodName name of the method in the subinterface
+   * @param text default text to use if no translation is available, which may be null
+   *     if no default text is supplied
+   * @param meaning extra text explaining the meaning of the text, or null if not needed
+   * @return the lookup key as a string or null if the key cannot be computed (for
+   *     example, if text is null but this generator requires it)
+   */
+  String generateKey(String className, String methodName, String text, String meaning);
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/i18n/rebind/keygen/MD5KeyGenerator.java b/user/src/com/google/gwt/i18n/rebind/keygen/MD5KeyGenerator.java
new file mode 100644
index 0000000..155b31f
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/keygen/MD5KeyGenerator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008 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.rebind.keygen;
+
+import com.google.gwt.dev.util.Util;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Key generator using the MD5 hash of the text and meaning.
+ */
+public class MD5KeyGenerator implements KeyGenerator {
+
+  public String generateKey(String className, String methodName, String text, String meaning) {
+    /*
+     * This does not use Util.computeStrongName because we would have
+     * to concatenate the text and meaning into a temporary buffer. 
+     */
+    
+    if (text == null) {
+      // Cannot compute a key if no default text is supplied.
+      return null;
+    }
+    MessageDigest md5;
+    try {
+      md5 = MessageDigest.getInstance("MD5");
+    } catch (NoSuchAlgorithmException e) {
+      throw new RuntimeException("Error initializing MD5", e);
+    }
+
+    try {
+      md5.update(text.getBytes("UTF-8"));
+      if (meaning != null) {
+        md5.update(meaning.getBytes("UTF-8"));
+      }
+    } catch (UnsupportedEncodingException e) {
+      throw new RuntimeException("UTF-8 unsupported", e);
+    }
+    return Util.toHexString(md5.digest());
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/rebind/keygen/MethodNameKeyGenerator.java b/user/src/com/google/gwt/i18n/rebind/keygen/MethodNameKeyGenerator.java
new file mode 100644
index 0000000..bb7976d
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/rebind/keygen/MethodNameKeyGenerator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2008 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.rebind.keygen;
+
+
+/**
+ * Key generator using just the method name as the lookup key. Note: this is
+ * prone to collisions if multiple Messages classes are aggregated for
+ * translation, and is therefore only recommended for simple 1:1 uses.
+ */
+public class MethodNameKeyGenerator implements KeyGenerator {
+
+  public String generateKey(String className, String methodName, String text, String meaning) {
+    return methodName;
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java b/user/src/com/google/gwt/i18n/rebind/keygen/package-info.java
similarity index 66%
copy from user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
copy to user/src/com/google/gwt/i18n/rebind/keygen/package-info.java
index eaca740..63a88ac 100644
--- a/user/src/com/google/gwt/i18n/client/cldr/CldrImpl_he.java
+++ b/user/src/com/google/gwt/i18n/rebind/keygen/package-info.java
@@ -13,16 +13,11 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.client.cldr;
-
-import com.google.gwt.i18n.client.impl.CldrImpl;
 
 /**
- * Placeholder for generated file.
+ * Generators which compute the value of a key to use for looking up translated
+ * resources.  These exist to allow easy extension to proprietary or internal
+ * message catalog systems which use different algorithms to compute keys for
+ * message aggregation. 
  */
-public class CldrImpl_he extends CldrImpl {
-
-  public boolean isRTL() {
-    return true;
-  }
-}
+package com.google.gwt.i18n.rebind.keygen;
diff --git a/user/src/com/google/gwt/i18n/rebind/util/AbstractResource.java b/user/src/com/google/gwt/i18n/rebind/util/AbstractResource.java
deleted file mode 100644
index 4c2f693..0000000
--- a/user/src/com/google/gwt/i18n/rebind/util/AbstractResource.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright 2008 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.rebind.util;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.MissingResourceException;
-import java.util.Set;
-
-/**
- * AbstractResource serves the same purpose as java
- * ResourceBundle/PropertyResourceBundle.
- * <p>
- * Each <code>Resource</code> belongs to a resource tree, indicated by the
- * path attribute.
- * <p>
- * AbstractResource uses a Factory pattern rather than a single static method to
- * load itself given an abstract string path.
- * <p>
- * One advanced feature which should not be used outside the core GWT system is
- * that resources can have more than one parent, for instance pets_en_US could
- * have pets_en as one parent and animals_en_US as another. The alternative
- * parents have lower precedence than any primary parent. Each alternative
- * parent is associated with a separate resource tree.
- */
-public abstract class AbstractResource {
-  /**
-   * Error messages concerning missing keys should include the defined keys if
-   * the number of keys is below this threshold.
-   */
-  public static final int REPORT_KEYS_THRESHOLD = 30;
-
-  private final List<AbstractResource> alternativeParents = new ArrayList<AbstractResource>();
-
-  private Set<String> keySet;
-
-  private String localeName;
-
-  private String path;
-
-  private AbstractResource primaryParent;
-
-  /**
-   * @see java.util.ResourceBundle#getLocale()
-   */
-  public String getLocaleName() {
-    return localeName;
-  }
-
-  /**
-   * @see java.util.ResourceBundle#getObject(java.lang.String)
-   */
-  public final Object getObject(String key) {
-    Object s = getObjectAux(key, true);
-    if (s == null) {
-      String msg = "Cannot find '" + key + "' in " + this;
-      Set<String> keys = this.keySet();
-      if (keys.size() < REPORT_KEYS_THRESHOLD) {
-        msg = msg + ", keys found:\n\t" + keys;
-      }
-      throw new MissingResourceException(msg, key, key);
-    }
-    return s;
-  }
-
-  /**
-   * @see java.util.ResourceBundle#getString(java.lang.String)
-   */
-  public final String getString(String key) {
-    return (String) getObject(key);
-  }
-
-  /**
-   * Keys associated with this resource.
-   * 
-   * @return keys
-   */
-  public Set<String> keySet() {
-    if (keySet == null) {
-      keySet = new HashSet<String>();
-      addToKeySet(keySet);
-      if (primaryParent != null) {
-        primaryParent.addToKeySet(keySet);
-      }
-      for (int i = 0; i < alternativeParents.size(); i++) {
-        AbstractResource element = alternativeParents.get(i);
-        keySet.addAll(element.keySet());
-      }
-    }
-    return keySet;
-  }
-
-  @Override
-  public String toString() {
-    return "resource for " + path;
-  }
-
-  /**
-   * A multi-line representation of this object.
-   * 
-   * @return verbose string
-   */
-  public String toVerboseString() {
-    StringBuffer b = new StringBuffer();
-    toVerboseStringAux(0, b);
-    return b.toString();
-  }
-
-  void addAlternativeParent(AbstractResource parent) {
-    if (parent != null) {
-      alternativeParents.add(parent);
-    }
-  }
-
-  abstract void addToKeySet(Set<String> s);
-
-  void checkKeys() {
-    // If I don't have a parent, then I am a default node so do not need to
-    // conform
-    if (primaryParent == null) {
-      return;
-    }
-    for (String key : keySet()) {
-      if (primaryParent.getObjectAux(key, true) == null) {
-        for (int i = 0; i < alternativeParents.size(); i++) {
-          AbstractResource alt = alternativeParents.get(i);
-          if (alt.getObjectAux(key, true) != null) {
-            break;
-          }
-        }
-
-        throw new IllegalArgumentException(
-            key
-                + " is not a valid resource key as it does not occur in the default version of "
-                + this + " nor in any of " + alternativeParents);
-      }
-    }
-  }
-
-  final Object getObjectAux(String key, boolean useAlternativeParents) {
-    Object s = handleGetObject(key);
-    if (s != null) {
-      return s;
-    }
-    AbstractResource parent = this.getPrimaryParent();
-    if (parent != null) {
-      // Primary parents should not look at their alternative parents
-      s = parent.getObjectAux(key, false);
-    }
-    if ((s == null) && (alternativeParents.size() > 0)
-        && (useAlternativeParents)) {
-      for (int i = 0; (i < alternativeParents.size()) && (s == null); i++) {
-        // Alternate parents may look at their alternative parents.
-        AbstractResource altParent = alternativeParents.get(i);
-        s = altParent.getObjectAux(key, true);
-      }
-    }
-    return s;
-  }
-
-  String getPath() {
-    return path;
-  }
-
-  AbstractResource getPrimaryParent() {
-    return primaryParent;
-  }
-
-  abstract Object handleGetObject(String key);
-
-  void setLocaleName(String locale) {
-    this.localeName = locale;
-  }
-
-  void setPath(String path) {
-    this.path = path;
-  }
-
-  void setPrimaryParent(AbstractResource primaryParent) {
-    if (primaryParent == null) {
-      return;
-    }
-    this.primaryParent = primaryParent;
-  }
-
-  private void newLine(int indent, StringBuffer buf) {
-    buf.append("\n");
-    for (int i = 0; i < indent; i++) {
-      buf.append("\t");
-    }
-  }
-
-  private void toVerboseStringAux(int indent, StringBuffer buf) {
-    newLine(indent, buf);
-    buf.append(toString());
-    if (primaryParent != null) {
-      newLine(indent, buf);
-      buf.append("Primary Parent: ");
-      primaryParent.toVerboseStringAux(indent + 1, buf);
-    }
-    for (int i = 0; i < alternativeParents.size(); i++) {
-      newLine(indent, buf);
-      buf.append("Alternate Parent: ");
-      AbstractResource element = alternativeParents.get(i);
-      element.toVerboseStringAux(indent + 1, buf);
-    }
-  }
-}
diff --git a/user/src/com/google/gwt/i18n/rebind/util/ResourceFactory.java b/user/src/com/google/gwt/i18n/rebind/util/ResourceFactory.java
deleted file mode 100644
index 64e1c5e..0000000
--- a/user/src/com/google/gwt/i18n/rebind/util/ResourceFactory.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright 2007 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.rebind.util;
-
-import com.google.gwt.core.ext.typeinfo.JClassType;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.MissingResourceException;
-import java.util.Set;
-
-/**
- * Creates resources.
- */
-public abstract class ResourceFactory {
-  static class SimplePathTree extends AbstractPathTree {
-    String path;
-
-    SimplePathTree(String path) {
-      this.path = path;
-    }
-
-    @Override
-    public AbstractPathTree getChild(int i) {
-      throw new UnsupportedOperationException(
-          "Simple paths have no children, therefore cannot get child: " + i);
-    }
-
-    @Override
-    public String getPath() {
-      return path;
-    }
-
-    @Override
-    public int numChildren() {
-      return 0;
-    }
-  }
-
-  private abstract static class AbstractPathTree {
-    abstract AbstractPathTree getChild(int i);
-
-    abstract String getPath();
-
-    abstract int numChildren();
-  }
-
-  private static class ClassPathTree extends AbstractPathTree {
-    Class<?> javaInterface;
-
-    ClassPathTree(Class<?> javaInterface) {
-      this.javaInterface = javaInterface;
-    }
-
-    @Override
-    AbstractPathTree getChild(int i) {
-      // we expect to do this at most once, so no caching is used.
-      return new ClassPathTree(javaInterface.getInterfaces()[i]);
-    }
-
-    @Override
-    String getPath() {
-      return javaInterface.getName();
-    }
-
-    @Override
-    int numChildren() {
-      return javaInterface.getInterfaces().length;
-    }
-  }
-
-  private static class JClassTypePathTree extends AbstractPathTree {
-    JClassType javaInterface;
-
-    JClassTypePathTree(JClassType javaInterface) {
-      this.javaInterface = javaInterface;
-    }
-
-    @Override
-    AbstractPathTree getChild(int i) {
-      // we expect to do this at most once, so no caching is used.
-      return new JClassTypePathTree(javaInterface.getImplementedInterfaces()[i]);
-    }
-
-    /**
-     * Path is equivalent to javaInterface.getQualifiedName() except for inner
-     * classes.
-     * 
-     * @see com.google.gwt.i18n.rebind.util.ResourceFactory.AbstractPathTree#getPath()
-     */
-    @Override
-    String getPath() {
-      String name = getResourceName(javaInterface);
-      String packageName = javaInterface.getPackage().getName();
-      return packageName + "." + name;
-    }
-
-    @Override
-    int numChildren() {
-      return javaInterface.getImplementedInterfaces().length;
-    }
-  }
-
-  /**
-   * Represents default locale.
-   */
-  public static final String DEFAULT_TOKEN = "default";
-  public static final char LOCALE_SEPARATOR = '_';
-
-  public static final AbstractResource NOT_FOUND = new AbstractResource() {
-
-    @Override
-    void addToKeySet(Set<String> s) {
-      throw new IllegalStateException("Not found resource");
-    }
-
-    @Override
-    Object handleGetObject(String key) {
-      throw new IllegalStateException("Not found resource");
-    }
-  };
-
-  private static Map<String, AbstractResource> cache = new HashMap<String, AbstractResource>();
-
-  private static List<LocalizedPropertiesResource.Factory> loaders = new ArrayList<LocalizedPropertiesResource.Factory>();
-  static {
-    loaders.add(new LocalizedPropertiesResource.Factory());
-  }
-
-  /**
-   * Clears the resource cache.
-   */
-  public static void clearCache() {
-    cache.clear();
-  }
-
-  /**
-   * Gets the resource associated with the given interface.
-   * 
-   * @param javaInterface interface
-   * @param locale locale name
-   * @return the resource
-   */
-  public static AbstractResource getBundle(Class<?> javaInterface, String locale) {
-    if (javaInterface.isInterface() == false) {
-      throw new IllegalArgumentException(javaInterface
-          + " should be an interface.");
-    }
-    ClassPathTree path = new ClassPathTree(javaInterface);
-    return getBundleAux(path, locale, true);
-  }
-
-  /**
-   * Gets the resource associated with the given interface.
-   * 
-   * @param javaInterface interface
-   * @param locale locale name
-   * @return the resource
-   */
-  public static AbstractResource getBundle(JClassType javaInterface,
-      String locale) {
-    return getBundleAux(new JClassTypePathTree(javaInterface), locale, true);
-  }
-
-  /**
-   * Gets the resource associated with the given path.
-   * 
-   * @param path the path
-   * @param locale locale name
-   * @return the resource
-   */
-  public static AbstractResource getBundle(String path, String locale) {
-    return getBundleAux(new SimplePathTree(path), locale, true);
-  }
-
-  /**
-   * Given a locale name, derives the parent's locale name. For example, if
-   * the locale name is "en_US", the parent locale name would be "en". If the
-   * locale name is that of a top level locale (i.e. no '_' characters, such
-   * as "fr"), then the the parent locale name is that of the default locale.
-   * If the locale name is null, the empty string, or is already that of the
-   * default locale, then null is returned.
-   *
-   * @param localeName the locale name
-   * @return the parent's locale name
-   */
-  public static String getParentLocaleName(String localeName) {
-    if (localeName == null ||
-        localeName.length() == 0 ||
-        localeName.equals(DEFAULT_TOKEN)) {
-      return null;
-    }
-    int pos = localeName.lastIndexOf(LOCALE_SEPARATOR);
-    if (pos != -1) {
-      return localeName.substring(0, pos);
-    }
-    return DEFAULT_TOKEN;
-  }
-
-  public static String getResourceName(JClassType targetClass) {
-    String name = targetClass.getName();
-    if (targetClass.isMemberType()) {
-      name = name.replace('.', '$');
-    }
-    return name;
-  }
-
-  private static List<AbstractResource> findAlternativeParents(
-      ResourceFactory.AbstractPathTree tree, String locale) {
-    List<AbstractResource> altParents = null;
-    if (tree != null) {
-      altParents = new ArrayList<AbstractResource>();
-      for (int i = 0; i < tree.numChildren(); i++) {
-        ResourceFactory.AbstractPathTree child = tree.getChild(i);
-        AbstractResource altParent = getBundleAux(child, locale, false);
-        if (altParent != null) {
-          altParents.add(altParent);
-        }
-      }
-    }
-    return altParents;
-  }
-
-  private static AbstractResource findPrimaryParent(
-      ResourceFactory.AbstractPathTree tree, String locale) {
-
-    // If we are not in the default case, calculate parent
-    if (!DEFAULT_TOKEN.equals(locale)) {
-      return getBundleAux(tree, getParentLocaleName(locale), false);
-    }
-    return null;
-  }
-
-  private static AbstractResource getBundleAux(
-      ResourceFactory.AbstractPathTree tree, String locale, boolean required) {
-    String targetPath = tree.getPath();
-    ClassLoader loader = AbstractResource.class.getClassLoader();
-
-    if (locale == null || locale.length() == 0) {
-      // This should never happen, since the only legitimate user of this
-      // method traces back to AbstractLocalizableImplCreator. The locale
-      // that is passed in from AbstractLocalizableImplCreator is produced
-      // by the I18N property provider, which guarantees that the locale
-      // will not be of zero length or null. However, we add this check
-      // in here in the event that a future user of ResourceFactory does
-      // not obey this constraint.
-      locale = DEFAULT_TOKEN;
-    }
-
-    // Calculate baseName
-    String localizedPath = targetPath;
-    if (!DEFAULT_TOKEN.equals(locale)) {
-      localizedPath = targetPath + LOCALE_SEPARATOR + locale;
-    }
-    AbstractResource result = cache.get(localizedPath);
-    if (result != null) {
-      if (result == NOT_FOUND) {
-        return null;
-      } else {
-        return result;
-      }
-    }
-    String partualPath = localizedPath.replace('.', '/');
-    AbstractResource parent = findPrimaryParent(tree, locale);
-    List<AbstractResource> altParents = findAlternativeParents(tree, locale);
-
-    AbstractResource found = null;
-    for (int i = 0; i < loaders.size(); i++) {
-      ResourceFactory element = loaders.get(i);
-      String path = partualPath + "." + element.getExt();
-      InputStream m = loader.getResourceAsStream(path);
-      if (m != null) {
-        found = element.load(m);
-        found.setPath(partualPath);
-        found.setPrimaryParent(parent);
-        found.setLocaleName(locale);
-        for (int j = 0; j < altParents.size(); j++) {
-          AbstractResource altParent = altParents.get(j);
-          found.addAlternativeParent(altParent);
-        }
-        found.checkKeys();
-        break;
-      }
-    }
-    if (found == null) {
-      if (parent != null) {
-        found = parent;
-      } else {
-        found = NOT_FOUND;
-      }
-    }
-
-    cache.put(localizedPath, found);
-
-    if (found == NOT_FOUND) {
-      if (required) {
-        throw new MissingResourceException(
-          "Could not find any resource associated with " + tree.getPath(),
-            null, null);
-      } else {
-        return null;
-      }
-    }
-
-    // At this point, found cannot be equal to null or NOT_FOUND
-    return found;
-  }
-
-  abstract String getExt();
-
-  abstract AbstractResource load(InputStream m);
-}
diff --git a/user/src/com/google/gwt/i18n/tools/I18NSync.java b/user/src/com/google/gwt/i18n/tools/I18NSync.java
index cc72a89..cb3e15a 100644
--- a/user/src/com/google/gwt/i18n/tools/I18NSync.java
+++ b/user/src/com/google/gwt/i18n/tools/I18NSync.java
@@ -19,10 +19,10 @@
 import com.google.gwt.i18n.client.ConstantsWithLookup;
 import com.google.gwt.i18n.client.Localizable;
 import com.google.gwt.i18n.client.Messages;
+import com.google.gwt.i18n.rebind.AbstractLocalizableInterfaceCreator;
+import com.google.gwt.i18n.rebind.ConstantsInterfaceCreator;
 import com.google.gwt.i18n.rebind.LocalizableGenerator;
-import com.google.gwt.i18n.rebind.util.AbstractLocalizableInterfaceCreator;
-import com.google.gwt.i18n.rebind.util.ConstantsInterfaceCreator;
-import com.google.gwt.i18n.rebind.util.MessagesInterfaceCreator;
+import com.google.gwt.i18n.rebind.MessagesInterfaceCreator;
 import com.google.gwt.util.tools.ArgHandler;
 import com.google.gwt.util.tools.ArgHandlerExtra;
 import com.google.gwt.util.tools.ArgHandlerFlag;
diff --git a/user/src/com/google/gwt/user/rebind/AbstractGeneratorClassCreator.java b/user/src/com/google/gwt/user/rebind/AbstractGeneratorClassCreator.java
index 5ad8483..8dfce3f 100644
--- a/user/src/com/google/gwt/user/rebind/AbstractGeneratorClassCreator.java
+++ b/user/src/com/google/gwt/user/rebind/AbstractGeneratorClassCreator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -21,10 +21,13 @@
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.i18n.rebind.AbstractResource;
+import com.google.gwt.i18n.rebind.AbstractResource.MissingResourceException;
 
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Abstract functionality needed to create classes needed to supply generators.
@@ -102,12 +105,13 @@
    * Emits the new class.
    * 
    * @param logger
+   * @param locale 
    * @throws UnableToCompleteException
    */
-  public void emitClass(TreeLogger logger) throws UnableToCompleteException {
+  public void emitClass(TreeLogger logger, String locale) throws UnableToCompleteException {
     logger = branch(logger, branchMessage());
     classPrologue();
-    emitMethods(logger, targetClass);
+    emitMethods(logger, targetClass, locale);
     classEpilog();
     getWriter().println("}");
   }
@@ -116,6 +120,26 @@
     return targetClass;
   }
 
+  public UnableToCompleteException logMissingResource(TreeLogger logger, String during,
+      MissingResourceException e) {
+    String msg = "No resource found for key '" + e.getKey() + "'";
+    if (during != null) {
+      msg += " while " + during;
+    }
+    logger.log(TreeLogger.ERROR, msg, e);
+    TreeLogger searchedBranch = logger.branch(TreeLogger.WARN, "Searched the following resources:",
+        null);
+    for (AbstractResource resource : e.getSearchedResources()) {
+      TreeLogger resBranch = searchedBranch.branch(TreeLogger.WARN, resource.toString(), null);
+      Set<String> keys = resource.keySet();
+      TreeLogger keyBranch = resBranch.branch(TreeLogger.INFO, "List of keys found", null);
+      for (String key : keys) {
+        keyBranch.log(TreeLogger.INFO, key, null);
+      }
+    }
+    return new UnableToCompleteException();
+  }
+
   /**
    * Registers a method creator.
    * 
@@ -151,11 +175,12 @@
   /**
    * Emit method body, arguments are arg1...argN.
    * 
-   * @param logger TODO
+   * @param logger TreeLogger for logging
    * @param method method to generate
+   * @param locale locale for this generation
    * @throws UnableToCompleteException
    */
-  protected abstract void emitMethodBody(TreeLogger logger, JMethod method)
+  protected abstract void emitMethodBody(TreeLogger logger, JMethod method, String locale)
       throws UnableToCompleteException;
 
   /**
@@ -199,11 +224,11 @@
     return writer;
   }
 
-  private void emitMethods(TreeLogger logger, JClassType cur)
+  private void emitMethods(TreeLogger logger, JClassType cur, String locale)
       throws UnableToCompleteException {
     JMethod[] x = getAllInterfaceMethods(cur);
     for (int i = 0; i < x.length; i++) {
-      genMethod(logger, x[i]);
+      genMethod(logger, x[i], locale);
       getWriter().println();
     }
   }
@@ -212,9 +237,10 @@
    * Generates a method declaration for the given method.
    * 
    * @param method method to generate
+   * @param locale 
    * @throws UnableToCompleteException
    */
-  private void genMethod(TreeLogger logger, JMethod method)
+  private void genMethod(TreeLogger logger, JMethod method, String locale)
       throws UnableToCompleteException {
     String name = method.getName();
     String returnType = method.getReturnType().getQualifiedSourceName();
@@ -233,7 +259,11 @@
     String methodName = method.getName();
     TreeLogger branch = branch(logger, "Generating method body for "
         + methodName + "()");
-    emitMethodBody(branch, method);
+    try {
+      emitMethodBody(branch, method, locale);
+    } catch (MissingResourceException e) {
+      throw logMissingResource(branch, null, e);
+    }
     getWriter().outdent();
     getWriter().println("}");
   }
diff --git a/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java b/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java
index 4a0d396..e116c6c 100644
--- a/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java
+++ b/user/src/com/google/gwt/user/rebind/AbstractMethodCreator.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.i18n.rebind.AbstractResource;
 
 /**
  * Creates method factories depending upon the method type. Includes the core
@@ -25,6 +26,7 @@
  * the package space.
  */
 public abstract class AbstractMethodCreator extends AbstractSourceCreator {
+  
   /**
    * AbstractGeneratorClassCreator associated with the method currently in
    * process.
@@ -43,13 +45,13 @@
   /**
    * Generate the method body for the target method.
    * 
-   * @param logger
+   * @param logger TreeLogger for logging
    * @param targetMethod Method
-   * @param value Arbitrary value
+   * @param resource base resource to use for lookup
    * @throws UnableToCompleteException
    */
   public abstract void createMethodFor(TreeLogger logger, JMethod targetMethod,
-      String value) throws UnableToCompleteException;
+      String key, AbstractResource resource, String locale) throws UnableToCompleteException;
 
   /**
    * Prints to the current <code>AbstractGeneratorClassCreator</code>.
diff --git a/user/test/com/google/gwt/i18n/I18NSuite.java b/user/test/com/google/gwt/i18n/I18NSuite.java
index f5ea159..bcb8f23 100644
--- a/user/test/com/google/gwt/i18n/I18NSuite.java
+++ b/user/test/com/google/gwt/i18n/I18NSuite.java
@@ -27,7 +27,7 @@
 import com.google.gwt.i18n.client.NumberFormat_fr_Test;
 import com.google.gwt.i18n.client.NumberParse_en_Test;
 import com.google.gwt.i18n.client.NumberParse_fr_Test;
-import com.google.gwt.i18n.rebind.util.AbstractResourceTest;
+import com.google.gwt.i18n.rebind.AbstractResourceTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
 
 import junit.framework.Test;
diff --git a/user/test/com/google/gwt/i18n/client/ColorsAndShapes.java b/user/test/com/google/gwt/i18n/client/ColorsAndShapes.java
index 30fcc33..e747572 100644
--- a/user/test/com/google/gwt/i18n/client/ColorsAndShapes.java
+++ b/user/test/com/google/gwt/i18n/client/ColorsAndShapes.java
@@ -19,7 +19,8 @@
 import com.google.gwt.i18n.client.gen.Shapes;
 
 /**
- * TODO: document me.
+ * Test interface which combines two other Localizable interfaces as well as
+ * adding new ones.
  */
 public interface ColorsAndShapes extends Colors, Shapes {
   String[] myFavorites();
diff --git a/user/test/com/google/gwt/i18n/client/ColorsAndShapesAndConcepts.java b/user/test/com/google/gwt/i18n/client/ColorsAndShapesAndConcepts.java
index 246a6f7..f199d69 100644
--- a/user/test/com/google/gwt/i18n/client/ColorsAndShapesAndConcepts.java
+++ b/user/test/com/google/gwt/i18n/client/ColorsAndShapesAndConcepts.java
@@ -16,7 +16,7 @@
 package com.google.gwt.i18n.client;
 
 /**
- * TODO: document me.
+ * Further test of combining and extending Localizable interfaces.
  */
 public interface ColorsAndShapesAndConcepts extends ColorsAndShapes {
   /**
diff --git a/user/test/com/google/gwt/i18n/client/I18N2Test.java b/user/test/com/google/gwt/i18n/client/I18N2Test.java
index 3322fe5..c0ec9d6 100644
--- a/user/test/com/google/gwt/i18n/client/I18N2Test.java
+++ b/user/test/com/google/gwt/i18n/client/I18N2Test.java
@@ -20,14 +20,36 @@
 import com.google.gwt.i18n.client.gen.TestBadKeys;
 import com.google.gwt.junit.client.GWTTestCase;
 
+import java.util.Date;
+
 /**
- * TODO: document me.
+ * Test the same things as I18NTest but with a different module which
+ * uses different locales.
  */
 public class I18N2Test extends GWTTestCase {
   public String getModuleName() {
     return "com.google.gwt.i18n.I18N2Test";
   }
 
+  public void testAnnotatedMessages() {
+    TestAnnotatedMessages m = GWT.create(TestAnnotatedMessages.class);
+    assertEquals("Test me", m.basicText());
+    assertEquals("Once more, with meaning", m.withMeaning());
+    assertEquals("One argument: one", m.oneArgument("one"));
+    assertEquals("One argument, which is optional",
+        m.optionalArgument("where am I?"));
+    assertEquals("Two arguments, second and first, inverted",
+        m.invertedArguments("first", "second"));
+    assertEquals("Don't tell me I can't {quote things in braces}", m.quotedText());
+    assertEquals("This {0} would be an argument if not quoted", m.quotedArg());
+    assertEquals("Total is $11,305.01", m.currencyFormat(11305.01));
+    assertEquals("Default number format is 1,017.1", m.defaultNumberFormat(1017.1));
+    assertEquals("It is 12:01 PM on Saturday, December 1, 2007",
+        m.getTimeDate(new Date(107, 11, 1, 12, 1, 2)));
+    assertEquals("13 widgets", m.pluralWidgetsOther(13));
+//    assertEquals("A widget", m.pluralWidgetsOther(1));
+  }
+  
   public void testBadKeys() {
     TestBadKeys test = (TestBadKeys) GWT.create(TestBadKeys.class);
     assertEquals("zh_spacer", test.zh_spacer());
diff --git a/user/test/com/google/gwt/i18n/client/I18NTest.java b/user/test/com/google/gwt/i18n/client/I18NTest.java
index 1dbf88d..0099ac6 100644
--- a/user/test/com/google/gwt/i18n/client/I18NTest.java
+++ b/user/test/com/google/gwt/i18n/client/I18NTest.java
@@ -35,11 +35,11 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Date;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.MissingResourceException;
 import java.util.Set;
-import java.util.Map.Entry;
 
 /**
  * Tests Internationalization. Assumes locale is set to piglatin_UK
@@ -106,6 +106,70 @@
     assertEquals("Extend Protected Inner", extendProtectedInner);
   }
 
+  public void testAnnotatedConstants() {
+    TestAnnotatedConstants c = GWT.create(TestAnnotatedConstants.class);
+    assertEquals(14, c.fourteen());
+    assertFalse(c.isFalse());
+    assertTrue(c.isTrue());
+    assertArrayEquals(new String[] {"String array with one string"}, c.singleString());
+    assertArrayEquals(new String[] {"One", "Two", "Three,Comma"}, c.threeStrings());
+    assertEquals("Properties value #s need quoting!", c.propertiesQuoting());
+    Map<String,String> stringMap = c.stringMap();
+    assertTrue(stringMap.containsKey("key1"));
+    assertTrue(stringMap.containsKey("key2"));
+    assertEquals("value1", stringMap.get("key1"));
+    assertEquals("value2", stringMap.get("key2"));
+    assertEquals(2, stringMap.size());
+    stringMap = c.rawMap();
+    assertTrue(stringMap.containsKey("key1"));
+    assertTrue(stringMap.containsKey("key2"));
+    assertEquals("value1", stringMap.get("key1"));
+    assertEquals("value2", stringMap.get("key2"));
+    assertEquals(2, stringMap.size());
+    assertEquals("Test me", c.testMe());
+    assertEquals(13.7f, c.thirteenPointSeven());
+    assertEquals(3.14, c.threePointOneFour());
+    assertEquals("Once more, with meaning", c.withMeaning());
+  }
+  
+  public void testAnnotatedConstantsGenMD5() {
+    TestAnnotatedConstantsGenMD5 c = GWT.create(TestAnnotatedConstantsGenMD5.class);
+    assertEquals(14, c.fourteen());
+    assertFalse(c.isFalse());
+    assertTrue(c.isTrue());
+    assertArrayEquals(new String[] {"String array with one string"}, c.singleString());
+    assertArrayEquals(new String[] {"One", "Two"}, c.twoStrings());
+    Map<String,String> stringMap = c.stringMap();
+    assertTrue(stringMap.containsKey("key1"));
+    assertTrue(stringMap.containsKey("key2"));
+    assertEquals("value1", stringMap.get("key1"));
+    assertEquals("value2", stringMap.get("key2"));
+    assertEquals(2, stringMap.size());
+    assertEquals("Test me", c.testMe());
+    assertEquals(13.7f, c.thirteenPointSeven());
+    assertEquals(3.14, c.threePointOneFour());
+    assertEquals("Once more, with meaning", c.withMeaning());
+  }
+  
+  public void testAnnotatedMessages() {
+    TestAnnotatedMessages m = GWT.create(TestAnnotatedMessages.class);
+    assertEquals("Estay emay", m.basicText());
+    assertEquals("Oncay oremay, ithway eaningmay", m.withMeaning());
+    assertEquals("PL: One argument: one", m.oneArgument("one"));
+    assertEquals("PL: One argument (where am I?), which is optional",
+        m.optionalArgument("where am I?"));
+    assertEquals("Two arguments, second and first, inverted",
+        m.invertedArguments("first", "second")); // from default locale
+    assertEquals("PL: Don't tell me I can't {quote things in braces}", m.quotedText());
+    assertEquals("PL: This {0} would be an argument if not quoted", m.quotedArg());
+    assertEquals("PL: Total is $11,305.01", m.currencyFormat(11305.01));
+    assertEquals("PL: Default number format is 1,017.1", m.defaultNumberFormat(1017.1));
+    assertEquals("PL: It is 12:01 PM on Saturday, December 1, 2007",
+        m.getTimeDate(new Date(107, 11, 1, 12, 1, 2)));
+    assertEquals("PL: 13 widgets", m.pluralWidgetsOther(13));
+    assertEquals("Too many widgets to count (150) in pig-latin", m.pluralWidgetsOther(150));
+  }
+  
   public void testBindings() {
     TestBinding b = (TestBinding) GWT.create(TestBinding.class);
     assertEquals("default", b.a());
@@ -161,7 +225,7 @@
   public void testConstantMapABCD() {
     TestConstants types = (TestConstants) GWT.create(TestConstants.class);
 
-    Map map = types.mapABCD();
+    Map<String, String> map = types.mapABCD();
     assertEquals(4, map.size());
     assertEquals("valueA", map.get("keyA"));
     assertEquals("valueB", map.get("keyB"));
@@ -170,16 +234,16 @@
 
     assertNull(map.get("bogus"));
 
-    Set keys = map.keySet();
-    Iterator keyIter = keys.iterator();
+    Set<String> keys = map.keySet();
+    Iterator<String> keyIter = keys.iterator();
     assertEquals("keyA", keyIter.next());
     assertEquals("keyB", keyIter.next());
     assertEquals("keyC", keyIter.next());
     assertEquals("keyD", keyIter.next());
     assertFalse(keyIter.hasNext());
 
-    Collection values = map.values();
-    Iterator valueIter = values.iterator();
+    Collection<String> values = map.values();
+    Iterator<String> valueIter = values.iterator();
     assertEquals("valueA", valueIter.next());
     assertEquals("valueB", valueIter.next());
     assertEquals("valueC", valueIter.next());
@@ -220,15 +284,15 @@
 
     ConstantMap map = (ConstantMap) types.mapBACD();
 
-    Set keys = map.keySet();
-    Iterator keyIter = keys.iterator();
+    Set<String> keys = map.keySet();
+    Iterator<String> keyIter = keys.iterator();
     assertEquals("keyB", keyIter.next());
     assertEquals("keyA", keyIter.next());
     assertEquals("keyC", keyIter.next());
     assertEquals("keyD", keyIter.next());
 
-    Collection values = map.values();
-    Iterator valueIter = values.iterator();
+    Collection<String> values = map.values();
+    Iterator<String> valueIter = values.iterator();
     assertEquals("valueB", valueIter.next());
     assertEquals("valueA", valueIter.next());
     assertEquals("valueC", valueIter.next());
@@ -245,14 +309,14 @@
 
     assertEquals(1, map.size());
 
-    Set keys = map.keySet();
+    Set<String> keys = map.keySet();
     assertEquals(1, keys.size());
-    Iterator keyIter = keys.iterator();
+    Iterator<String> keyIter = keys.iterator();
     assertEquals("keyB", keyIter.next());
 
-    Collection values = map.values();
+    Collection<String> values = map.values();
     assertEquals(1, values.size());
-    Iterator valueIter = values.iterator();
+    Iterator<String> valueIter = values.iterator();
     assertEquals("valueB", valueIter.next());
   }
 
@@ -264,15 +328,15 @@
 
     ConstantMap map = (ConstantMap) types.mapDCBA();
 
-    Set keys = map.keySet();
-    Iterator keyIter = keys.iterator();
+    Set<String> keys = map.keySet();
+    Iterator<String> keyIter = keys.iterator();
     assertEquals("keyD", keyIter.next());
     assertEquals("keyC", keyIter.next());
     assertEquals("keyB", keyIter.next());
     assertEquals("keyA", keyIter.next());
 
-    Collection values = map.values();
-    Iterator valueIter = values.iterator();
+    Collection<String> values = map.values();
+    Iterator<String> valueIter = values.iterator();
     assertEquals("valueD", valueIter.next());
     assertEquals("valueC", valueIter.next());
     assertEquals("valueB", valueIter.next());
@@ -289,32 +353,32 @@
 
     assertEquals(3, map.size());
 
-    Set keys = map.keySet();
+    Set<String> keys = map.keySet();
     assertEquals(3, keys.size());
-    Iterator keyIter = keys.iterator();
+    Iterator<String> keyIter = keys.iterator();
     assertEquals("keyX", keyIter.next());
     assertEquals("keyY", keyIter.next());
     assertEquals("keyZ", keyIter.next());
 
-    Collection values = map.values();
+    Collection<String> values = map.values();
     assertEquals(3, values.size());
-    Iterator valueIter = values.iterator();
+    Iterator<String> valueIter = values.iterator();
     assertEquals("valueZ", valueIter.next());
     assertEquals("valueZ", valueIter.next());
     assertEquals("valueZ", valueIter.next());
 
-    Set entries = map.entrySet();
+    Set<Map.Entry<String, String>> entries = map.entrySet();
     assertEquals(3, entries.size());
-    Iterator entryIter = entries.iterator();
-    Map.Entry entry;
+    Iterator<Map.Entry<String, String>> entryIter = entries.iterator();
+    Map.Entry<String, String> entry;
 
-    entry = (Entry) entryIter.next();
+    entry = entryIter.next();
     assertEquals("keyX", entry.getKey());
     assertEquals("valueZ", entry.getValue());
-    entry = (Entry) entryIter.next();
+    entry = entryIter.next();
     assertEquals("keyY", entry.getKey());
     assertEquals("valueZ", entry.getValue());
-    entry = (Entry) entryIter.next();
+    entry = entryIter.next();
     assertEquals("keyZ", entry.getKey());
     assertEquals("valueZ", entry.getValue());
   }
@@ -412,14 +476,14 @@
     assertEquals("3 {2},{2},{2}, one {0}, two {1} {1}",
         d.get("formattedMessage"));
     assertEquals("4", d.get("d"));
-    Set s = d.keySet();
+    Set<String> s = d.keySet();
     assertTrue(s.contains("a"));
     assertTrue(s.contains("b"));
     assertFalse(s.contains("c"));
-    Collection s2 = d.values();
+    Collection<String> s2 = d.values();
     assertTrue(s2.contains("A"));
     assertTrue(s2.contains("B"));
-    Iterator iter = s2.iterator();
+    Iterator<String> iter = s2.iterator();
     assertEquals("3 {2},{2},{2}, one {0}, two {1} {1}", iter.next());
     assertEquals(4, s2.size());
     Dictionary empty = Dictionary.getDictionary("emptyDic");
@@ -471,8 +535,8 @@
   public void testTypedMessages() {
     TestTypedMessages typed = (TestTypedMessages) GWT.create(TestTypedMessages.class);
     String expected = "int(0) float(1.2), long(0), boolean(true), Object([], char(a), byte(127), short(-32768);";
-    assertEquals(expected, typed.testAllTypes(0, (float) 1.2, 0, true,
-        new ArrayList(), 'a', Byte.MAX_VALUE, Short.MIN_VALUE));
+    assertEquals(expected, typed.testAllTypes(0, (float) 1.2, 0, true, new ArrayList<String>(),
+        'a', Byte.MAX_VALUE, Short.MIN_VALUE));
     String lotsOfInts = typed.testLotsOfInts(1, 2, 3, 4);
     assertEquals("1, 2,3,4 ", lotsOfInts);
     String oneFloat = typed.simpleMessageTest((float) 2.3);
@@ -482,8 +546,8 @@
     String testSomeObjectTypes = typed.testSomeObjectTypes(new I18NTest(),
         new StringBuffer("hello"), new Integer("34"), null);
     assertEquals(
-        "this(null(com.google.gwt.i18n.client.I18NTest)), StringBuffer(hello), Integer(34), null(null);",
-        testSomeObjectTypes);
+        "this(null(com.google.gwt.i18n.client.I18NTest)), StringBuffer(hello), Integer(34), "
+        + "null(null);", testSomeObjectTypes);
   }
 
   private void assertArrayEquals(String[] shouldBe, String[] test) {
@@ -494,12 +558,12 @@
   }
 
   private native void createDummyDictionaries() /*-{
-   $wnd.testDic = new Object();
-   $wnd.testDic.formattedMessage = "3 {2},{2},{2}, one {0}, two {1} {1}";
-   $wnd.testDic.a="A";
-   $wnd.testDic.b="B";
-   $wnd.testDic.d=4;
-   $wnd.emptyDic = new Object();
-   $wnd.malformedDic = 4;
-   }-*/;
+    $wnd.testDic = new Object();
+    $wnd.testDic.formattedMessage = "3 {2},{2},{2}, one {0}, two {1} {1}";
+    $wnd.testDic.a="A";
+    $wnd.testDic.b="B";
+    $wnd.testDic.d=4;
+    $wnd.emptyDic = new Object();
+    $wnd.malformedDic = 4;
+  }-*/;
 }
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedConstants.java b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstants.java
new file mode 100644
index 0000000..352d7e9
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstants.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2008 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.client;
+
+import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
+import com.google.gwt.i18n.client.LocalizableResource.Generate;
+import com.google.gwt.i18n.client.LocalizableResource.GenerateKeys;
+
+import java.util.Map;
+
+/**
+ * Test of Constants generation using annotations.
+ */
+@DefaultLocale("en-US")
+@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MethodNameKeyGenerator") // default
+@Generate(format = "com.google.gwt.i18n.rebind.format.Properties")
+public interface TestAnnotatedConstants extends Constants {
+
+  @DefaultIntValue(14)
+  int fourteen();
+  
+  @DefaultBooleanValue(false)
+  boolean isFalse();
+  
+  @DefaultBooleanValue(true)
+  boolean isTrue();
+  
+  @Key(" properties key ")
+  @DefaultStringValue("Key with whitespace")
+  String propertiesKey();
+  
+  @DefaultStringValue("Properties value #s need quoting!")
+  String propertiesQuoting();
+  
+  @DefaultStringValue("   Check that leading spaces are quoted and trailing ones aren't   ")
+  String propertiesSpaces();
+  
+  @DefaultStringArrayValue("String array with one string")
+  String[] singleString();
+  
+  @DefaultStringMapValue({"key1", "value1", "key2", "value2"})
+  Map<String, String> stringMap();
+
+  @SuppressWarnings("unchecked") // intentional test of raw Map type
+  @DefaultStringMapValue({"key1", "value1", "key2", "value2"})
+  Map rawMap();
+
+  @DefaultStringValue("Test me")
+  String testMe();
+  
+  @DefaultFloatValue(13.7f)
+  float thirteenPointSeven();
+  
+  @DefaultDoubleValue(3.14)
+  double threePointOneFour();
+  
+  @DefaultStringArrayValue({"One", "Two", "Three,Comma"})
+  String[] threeStrings();
+  
+  @DefaultStringValue("Once more, with meaning")
+  @Meaning("Mangled quote")
+  String withMeaning();
+}
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedConstantsGenMD5.java b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstantsGenMD5.java
new file mode 100644
index 0000000..77ec6ac
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedConstantsGenMD5.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2008 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.client;
+
+import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
+import com.google.gwt.i18n.client.LocalizableResource.Generate;
+import com.google.gwt.i18n.client.LocalizableResource.GenerateKeys;
+
+import java.util.Map;
+
+/**
+ * Test of Constants generation using annotations.
+ */
+@DefaultLocale("en-US")
+@GenerateKeys // ("com.google.gwt.i18n.rebind.MD5") - default
+@Generate(format = "com.google.gwt.i18n.rebind.format.Properties")
+public interface TestAnnotatedConstantsGenMD5 extends Constants {
+
+  @DefaultIntValue(14)
+  int fourteen();
+  
+  @DefaultBooleanValue(false)
+  boolean isFalse();
+  
+  @DefaultBooleanValue(true)
+  boolean isTrue();
+  
+  @DefaultStringArrayValue("String array with one string")
+  String[] singleString();
+  
+  @DefaultStringMapValue({"key1", "value1", "key2", "value2"})
+  Map<String, String> stringMap();
+
+  @DefaultStringValue("Test me")
+  String testMe();
+  
+  @DefaultFloatValue(13.7f)
+  float thirteenPointSeven();
+  
+  @DefaultDoubleValue(3.14)
+  double threePointOneFour();
+  
+  @DefaultStringArrayValue({"One", "Two"})
+  String[] twoStrings();
+  
+  @DefaultStringValue("Once more, with meaning")
+  @Meaning("Mangled quote")
+  String withMeaning();
+}
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
new file mode 100644
index 0000000..0dbe991
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2006 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.client;
+
+import com.google.gwt.i18n.client.LocalizableResource.DefaultLocale;
+import com.google.gwt.i18n.client.LocalizableResource.Generate;
+import com.google.gwt.i18n.client.LocalizableResource.GenerateKeys;
+
+import java.util.Date;
+
+/**
+ * Test of Messages generation using annotations.
+ */
+@DefaultLocale("en-US")
+//@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator")
+@GenerateKeys("com.google.gwt.i18n.rebind.keygen.MethodNameKeyGenerator") // default
+@Generate(format = "com.google.gwt.i18n.rebind.format.Properties")
+public interface TestAnnotatedMessages extends Messages {
+
+  @DefaultMessage("Test me")
+  String basicText();
+  
+  @DefaultMessage("Once more, with meaning")
+  @Meaning("Mangled quote")
+  String withMeaning();
+  
+  @DefaultMessage("One argument: {0}")
+  String oneArgument(String value);
+  
+  @DefaultMessage("One argument, which is optional")
+  String optionalArgument(@Optional String value);
+  
+  @DefaultMessage("Two arguments, {1} and {0}, inverted")
+  String invertedArguments(String one, String two);
+  
+  @DefaultMessage("Don''t tell me I can''t '{'quote things in braces'}'")
+  String quotedText();
+  
+  @DefaultMessage("This '{0}' would be an argument if not quoted")
+  String quotedArg();
+  
+  @DefaultMessage("Total is {0,number,currency}")
+  String currencyFormat(double value);
+  
+  @DefaultMessage("Default number format is {0,number}")
+  String defaultNumberFormat(double value);
+  
+  @DefaultMessage("It is {0,time,short} on {0,date,full}")
+  String getTimeDate(Date value);
+  
+  @DefaultMessage("{0} widgets")
+  @PluralText({"one", "A widget"})
+  String pluralWidgetsOther(@PluralCount int count);
+
+  @DefaultMessage("{1} {0}")
+  @PluralText({"one", "A {0}"})
+  String twoParamPlural(String name, @PluralCount int count);
+}
diff --git a/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_piglatin.properties b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_piglatin.properties
new file mode 100644
index 0000000..0903c7d
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages_piglatin.properties
@@ -0,0 +1,12 @@
+basicText=Estay emay
+withMeaning=Oncay oremay, ithway eaningmay
+oneArgument=PL: One argument: {0}
+optionalArgument=PL: One argument ({0}), which is optional
+quotedText=PL: Don''t tell me I can''t '{'quote things in braces'}'
+quotedArg=PL: This '{0}' would be an argument if not quoted
+currencyFormat=PL: Total is {0,number,currency}
+defaultNumberFormat=PL: Default number format is {0,number}
+getTimeDate=PL: It is {0,time,short} on {0,date,full}
+pluralWidgetsOther=PL: {0} widgets
+pluralWidgetsOther[many]=Too many widgets to count ({0}) in pig-latin
+pluralWidgetsOther[one]=PL: A widget
diff --git a/user/test/com/google/gwt/i18n/client/TestTypedMessages.properties b/user/test/com/google/gwt/i18n/client/TestTypedMessages.properties
index 07257cd..060db2d 100644
--- a/user/test/com/google/gwt/i18n/client/TestTypedMessages.properties
+++ b/user/test/com/google/gwt/i18n/client/TestTypedMessages.properties
@@ -1,6 +1,6 @@
 testAllTypes = int({0}) float({1}), long({2}), boolean({3}), Object({4}, char({5}), byte({6}), short({7});
 testSomeObjectTypes = this({0}), StringBuffer({1}), Integer({2}), null({3});
 testLotsOfInts = {0}, {1},{2},{3} 
-testSingleQuotes = ''A'', ''{0}'', '','''
+testSingleQuotes = ''A'', ''{0}'', '',''
 simpleMessageTest={0}
 stringEscaping= "\ \\ \\\ & \t	\n\r\"{0}	
\ No newline at end of file
diff --git a/user/test/com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-override.properties b/user/test/com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-override.properties
similarity index 100%
rename from user/test/com/google/gwt/i18n/client/cldr/LocaleNativeDisplayNames-override.properties
rename to user/test/com/google/gwt/i18n/client/impl/cldr/LocaleNativeDisplayNames-override.properties
diff --git a/user/test/com/google/gwt/i18n/client/impl/plurals/DefaultRule_piglatin.java b/user/test/com/google/gwt/i18n/client/impl/plurals/DefaultRule_piglatin.java
new file mode 100644
index 0000000..3949a47
--- /dev/null
+++ b/user/test/com/google/gwt/i18n/client/impl/plurals/DefaultRule_piglatin.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2008 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.client.impl.plurals;
+
+/**
+ * If no other rule applies, do not use any special plural forms.
+ */
+public class DefaultRule_piglatin extends DefaultRule {
+
+  @Override
+  public int select(int n) {
+    return n == 1 ? 1 : n > 100 ? 2 : 0;
+  }
+
+  @Override
+  public PluralForm[] pluralForms() {
+    return new PluralForm[] {
+        new PluralForm("other", "Default plural form"),
+        new PluralForm("one", "Count is 1"),
+        new PluralForm("many", "Count is over 100"),
+    };
+  }
+}
diff --git a/user/test/com/google/gwt/i18n/rebind/util/AbstractResourceTest.java b/user/test/com/google/gwt/i18n/rebind/AbstractResourceTest.java
similarity index 92%
rename from user/test/com/google/gwt/i18n/rebind/util/AbstractResourceTest.java
rename to user/test/com/google/gwt/i18n/rebind/AbstractResourceTest.java
index f5e26bb..572d6ca 100644
--- a/user/test/com/google/gwt/i18n/rebind/util/AbstractResourceTest.java
+++ b/user/test/com/google/gwt/i18n/rebind/AbstractResourceTest.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.i18n.rebind.util;
+package com.google.gwt.i18n.rebind;
 
 import com.google.gwt.i18n.client.ColorsAndShapes;
 import com.google.gwt.i18n.client.ColorsAndShapesAndConcepts;
@@ -38,10 +38,10 @@
   public void testBundle() {
     // simple test
     String s = Colors.class.getName();
-    AbstractResource resource = ResourceFactory.getBundle(s, null);
+    AbstractResource resource = ResourceFactory.getBundle(s, null, true);
     assertNotNull(resource);
     AbstractResource pigLatinResource =
-        ResourceFactory.getBundle(s, LOCALE_NAME_PIGLATIN);
+        ResourceFactory.getBundle(s, LOCALE_NAME_PIGLATIN, true);
     assertEquals(LOCALE_NAME_PIGLATIN, pigLatinResource.getLocaleName());
     assertNotNull(pigLatinResource);
     assertEquals("ueblay", pigLatinResource.getString("blue"));
@@ -51,7 +51,7 @@
   public void testInheritence() {
     ResourceFactory.clearCache();
     AbstractResource resource = ResourceFactory.getBundle(
-      ColorsAndShapes.class, LOCALE_NAME_PIGLATIN);
+      ColorsAndShapes.class, LOCALE_NAME_PIGLATIN, true);
     assertEquals(LOCALE_NAME_PIGLATIN, resource.getLocaleName());
     assertEquals("ueblay", resource.getString("blue"));
     assertEquals("ĝréý", resource.getString("grey"));
@@ -69,7 +69,7 @@
 
   public void testDoubleInherits() {
     AbstractResource resource = ResourceFactory.getBundle(
-      ColorsAndShapesAndConcepts.class, LOCALE_NAME_PIGLATIN_UK);
+      ColorsAndShapesAndConcepts.class, LOCALE_NAME_PIGLATIN_UK, true);
     String s = resource.getString("internationalization");
     assertEquals("Îñţérñåţîöñåļîžåţîöñ", s);
     assertTrue(resource.keySet().size() > 5);
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index d41b6b8..fe3fcfc 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -15,6 +15,7 @@
  */

 package com.google.gwt.user.rebind.rpc;

 

+import com.google.gwt.core.ext.BadPropertyValueException;

 import com.google.gwt.core.ext.PropertyOracle;

 import com.google.gwt.core.ext.TreeLogger;

 import com.google.gwt.core.ext.UnableToCompleteException;

@@ -83,8 +84,9 @@
       return "";

     }

 

-    public String[] getPropertyValueSet(TreeLogger logger, String propertyName) {

-      return new String[] {};

+    public String[] getPropertyValueSet(TreeLogger logger, String propertyName)

+        throws BadPropertyValueException {

+      return new String[] { "" };

     }

   }