Convert libaddressinput validation error codes into error messages.
diff --git a/cpp/include/libaddressinput/address_data.h b/cpp/include/libaddressinput/address_data.h
index ea371fe..5718dd3 100644
--- a/cpp/include/libaddressinput/address_data.h
+++ b/cpp/include/libaddressinput/address_data.h
@@ -68,6 +68,11 @@
   // which comprises multiple fields (will crash otherwise).
   const std::vector<std::string>& GetRepeatedFieldValue(
       AddressField field) const;
+
+  // Returns true if the parameter comprises multiple fields, false otherwise.
+  // Use it to determine whether to call |GetFieldValue| or
+  // |GetRepeatedFieldValue|.
+  static bool IsRepeatedFieldValue(AddressField field);
 };
 
 }  // namespace addressinput
diff --git a/cpp/include/libaddressinput/localization.h b/cpp/include/libaddressinput/localization.h
index 77a1917..c25ad4a 100644
--- a/cpp/include/libaddressinput/localization.h
+++ b/cpp/include/libaddressinput/localization.h
@@ -15,11 +15,17 @@
 #ifndef I18N_ADDRESSINPUT_LOCALIZATION_H_
 #define I18N_ADDRESSINPUT_LOCALIZATION_H_
 
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_problem.h>
+
 #include <string>
+#include <vector>
 
 namespace i18n {
 namespace addressinput {
 
+struct AddressData;
+
 // The object to retrieve localized strings based on message IDs. Sample usage:
 //    Localization localization;
 //    localization.SetLanguage("en");
@@ -41,6 +47,22 @@
   // there's no message with this identifier.
   std::string GetString(int message_id) const;
 
+  // Returns the error message. If |enable_examples| is false, then the error
+  // message will not contain examples of valid input. If |enable_links| is
+  // false, then the error message will not contain HTML links. (Some error
+  // messages contain postal code examples or link to post office websites to
+  // look up the postal code for an address). Vector field values (e.g. for
+  // street address) should not be empty if problem is UNKNOWN_VALUE. The
+  // POSTAL_CODE field should only be used with MISSING_REQUIRED_FIELD,
+  // INVALID_FORMAT, and MISMATCHING_VALUE problem codes. All other fields
+  // should only be used with MISSING_REQUIRED_FIELD, UNKNOWN_VALUE, and
+  // USES_P_O_BOX problem codes.
+  std::string GetErrorMessage(const AddressData& address,
+                              AddressField field,
+                              AddressProblem problem,
+                              bool enable_examples,
+                              bool enable_links);
+
   // Sets the language for the strings. The only supported language is "en"
   // until we have translations.
   void SetLanguage(const std::string& language_tag);
@@ -54,6 +76,22 @@
   const std::string& GetLanguage() const { return language_tag_; }
 
  private:
+  // Returns the error message where the address field is a postal code. Helper
+  // to |GetErrorMessage|. If |postal_code_example| is empty, then the error
+  // message will not contain examples of valid postal codes. If
+  // |post_service_url| is empty, then the error message will not contain a post
+  // service URL. The problem should only be one of MISSING_REQUIRED_FIELD,
+  // INVALID_FORMAT, or MISMATCHING_VALUE.
+  std::string GetErrorMessageForPostalCode(const AddressData& address,
+                                           AddressProblem problem,
+                                           bool uses_postal_code_as_label,
+                                           std::string postal_code_example,
+                                           std::string post_service_url);
+
+  // Calls |parameters.push_back| with 2 strings: the opening and closing tags
+  // of the given URL's HTML link.
+  void PushBackUrl(std::vector<std::string>& parameters, const std::string url);
+
   // The string getter.
   std::string (*get_string_)(int);
 
diff --git a/cpp/src/address_data.cc b/cpp/src/address_data.cc
index 8e2fdc6..7c285b8 100644
--- a/cpp/src/address_data.cc
+++ b/cpp/src/address_data.cc
@@ -97,5 +97,10 @@
   return this->*kVectorStringField[field];
 }
 
+// static
+bool AddressData::IsRepeatedFieldValue(AddressField field) {
+  return field == STREET_ADDRESS;
+}
+
 }  // namespace addressinput
 }  // namespace i18n
diff --git a/cpp/src/address_ui.cc b/cpp/src/address_ui.cc
index 7933794..d515765 100644
--- a/cpp/src/address_ui.cc
+++ b/cpp/src/address_ui.cc
@@ -37,10 +37,10 @@
 
 namespace {
 
-std::string GetString(const Localization& localization,
-                      AddressField field,
-                      int admin_area_name_message_id,
-                      int postal_code_name_message_id) {
+std::string GetLabelForField(const Localization& localization,
+                             AddressField field,
+                             int admin_area_name_message_id,
+                             int postal_code_name_message_id) {
   int messageId;
   switch (field) {
     case SORTING_CODE:
@@ -126,7 +126,7 @@
                                 : AddressUiComponent::HINT_SHORT;
     preceded_by_newline = false;
     component.field = format_it->GetField();
-    component.name = GetString(localization, format_it->GetField(),
+    component.name = GetLabelForField(localization, format_it->GetField(),
         rule.GetAdminAreaNameMessageId(), rule.GetPostalCodeNameMessageId());
     result.push_back(component);
   }
diff --git a/cpp/src/localization.cc b/cpp/src/localization.cc
index 1558b23..6b16e35 100644
--- a/cpp/src/localization.cc
+++ b/cpp/src/localization.cc
@@ -14,10 +14,19 @@
 
 #include <libaddressinput/localization.h>
 
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_problem.h>
+
 #include <cassert>
 #include <cstddef>
 #include <string>
 
+#include "grit.h"
+#include "region_data_constants.h"
+#include "rule.h"
+#include "util/string_util.h"
+
 namespace i18n {
 namespace addressinput {
 
@@ -52,6 +61,57 @@
   return get_string_(message_id);
 }
 
+std::string Localization::GetErrorMessage(const AddressData& address,
+                                          AddressField field,
+                                          AddressProblem problem,
+                                          bool enable_examples,
+                                          bool enable_links) {
+  if (field == POSTAL_CODE) {
+    Rule rule;
+    rule.CopyFrom(Rule::GetDefault());
+    std::string postal_code_example, post_service_url;
+    if (rule.ParseSerializedRule(
+            RegionDataConstants::GetRegionData(address.region_code))) {
+      if (enable_examples) {
+        postal_code_example = rule.GetPostalCodeExample();
+      }
+      if (enable_links) {
+        post_service_url = rule.GetPostServiceUrl();
+      }
+    }
+    // If we can't parse the serialized rule |uses_postal_code_as_label| will be
+    // determined from the default rule.
+    bool uses_postal_code_as_label =
+        rule.GetPostalCodeNameMessageId() ==
+        IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL;
+    return GetErrorMessageForPostalCode(address, problem,
+                                        uses_postal_code_as_label,
+                                        postal_code_example, post_service_url);
+  } else {
+    if (problem == MISSING_REQUIRED_FIELD) {
+      return get_string_(IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD);
+    } else if (problem == UNKNOWN_VALUE) {
+      std::vector<std::string> parameters;
+      if (AddressData::IsRepeatedFieldValue(field)) {
+        std::vector<std::string> values = address.GetRepeatedFieldValue(field);
+        assert(!values.empty());
+        parameters.push_back(values.front());
+      } else {
+        parameters.push_back(address.GetFieldValue(field));
+      }
+      return DoReplaceStringPlaceholders(
+          get_string_(IDS_LIBADDRESSINPUT_UNKNOWN_VALUE), parameters);
+    } else if (problem == USES_P_O_BOX) {
+      return get_string_(IDS_LIBADDRESSINPUT_PO_BOX_FORBIDDEN_VALUE);
+    } else {
+      // Keep the default under "else" so the compiler helps us check that all
+      // handled cases return and don't fall through.
+      assert(false);
+      return "";
+    }
+  }
+}
+
 void Localization::SetLanguage(const std::string& language_tag) {
   if (language_tag == kDefaultLanguage) {
     get_string_ = &en::GetStdString;
@@ -68,5 +128,74 @@
   language_tag_ = language_tag;
 }
 
+std::string Localization::GetErrorMessageForPostalCode(
+    const AddressData& address,
+    AddressProblem problem,
+    bool uses_postal_code_as_label,
+    std::string postal_code_example,
+    std::string post_service_url) {
+  int message_id;
+  std::vector<std::string> parameters;
+  if (problem == MISSING_REQUIRED_FIELD) {
+    if (!postal_code_example.empty() && !post_service_url.empty()) {
+      message_id = uses_postal_code_as_label ?
+          IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE_AND_URL :
+          IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE_AND_URL;
+      parameters.push_back(postal_code_example);
+      Localization::PushBackUrl(parameters, post_service_url);
+    } else if (!postal_code_example.empty()) {
+      message_id = uses_postal_code_as_label ?
+          IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE :
+          IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE ;
+      parameters.push_back(postal_code_example);
+    } else {
+      message_id = IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD;
+    }
+    return DoReplaceStringPlaceholders(get_string_(message_id), parameters);
+  } else if (problem == INVALID_FORMAT) {
+    if (!postal_code_example.empty() && !post_service_url.empty()) {
+      message_id = uses_postal_code_as_label ?
+          IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE_AND_URL :
+          IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE_AND_URL;
+      parameters.push_back(postal_code_example);
+      Localization::PushBackUrl(parameters, post_service_url);
+    } else if (!postal_code_example.empty()) {
+      message_id = uses_postal_code_as_label ?
+          IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE :
+          IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE;
+      parameters.push_back(postal_code_example);
+    } else {
+      message_id = uses_postal_code_as_label ?
+          IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE :
+          IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP;
+    }
+    return DoReplaceStringPlaceholders(get_string_(message_id), parameters);
+  } else if (problem == MISMATCHING_VALUE) {
+    if (!post_service_url.empty()) {
+      message_id = uses_postal_code_as_label ?
+          IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE_URL :
+          IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP_URL;
+      Localization::PushBackUrl(parameters, post_service_url);
+    } else {
+      message_id = uses_postal_code_as_label ?
+          IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE :
+          IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP;
+    }
+    return DoReplaceStringPlaceholders(get_string_(message_id), parameters);
+  } else {
+    // Keep the default under "else" so the compiler helps us check that all
+    // handled cases return and don't fall through.
+    assert(false);
+    return "";
+  }
+}
+
+void Localization::PushBackUrl(std::vector<std::string>& parameters,
+                               const std::string url) {
+  // TODO: HTML-escape the "url".
+  parameters.push_back("<a href=\"" + url + "\">");
+  parameters.push_back("</a>");
+}
+
 }  // namespace addressinput
 }  // namespace i18n
diff --git a/cpp/src/rule.cc b/cpp/src/rule.cc
index 614f400..68d34a9 100644
--- a/cpp/src/rule.cc
+++ b/cpp/src/rule.cc
@@ -48,6 +48,8 @@
 const char kRequireKey[] = "require";
 const char kSubKeysKey[] = "sub_keys";
 const char kZipKey[] = "zip";
+const char kPostalCodeExampleKey[] = "zipex";
+const char kPostServiceUrlKey[] = "posturl";
 
 // Used as a separator in a list of items. For example, the list of supported
 // languages can be "de~fr~it".
@@ -119,7 +121,9 @@
       admin_area_name_message_id_(INVALID_MESSAGE_ID),
       postal_code_name_message_id_(INVALID_MESSAGE_ID),
       name_(),
-      latin_name_() {}
+      latin_name_(),
+      postal_code_example_(),
+      post_service_url_() {}
 
 Rule::~Rule() {}
 
@@ -151,6 +155,8 @@
   postal_code_name_message_id_ = rule.postal_code_name_message_id_;
   name_ = rule.name_;
   latin_name_ = rule.latin_name_;
+  postal_code_example_ = rule.postal_code_example_;
+  post_service_url_ = rule.post_service_url_;
 }
 
 bool Rule::ParseSerializedRule(const std::string& serialized_rule) {
@@ -233,6 +239,14 @@
   if (json.HasStringValueForKey(kLatinNameKey)) {
     latin_name_ = json.GetStringValueForKey(kLatinNameKey);
   }
+
+  if (json.HasStringValueForKey(kPostalCodeExampleKey)) {
+    postal_code_example_ = json.GetStringValueForKey(kPostalCodeExampleKey);
+  }
+
+  if (json.HasStringValueForKey(kPostServiceUrlKey)) {
+    post_service_url_ = json.GetStringValueForKey(kPostServiceUrlKey);
+  }
 }
 
 }  // namespace addressinput
diff --git a/cpp/src/rule.h b/cpp/src/rule.h
index 6e57d70..4954b98 100644
--- a/cpp/src/rule.h
+++ b/cpp/src/rule.h
@@ -116,6 +116,16 @@
   // rule, if there is one.
   const std::string& GetLatinName() const { return latin_name_; }
 
+  // Returns the postal code example string for this rule.
+  const std::string& GetPostalCodeExample() const {
+    return postal_code_example_;
+  }
+
+  // Returns the post service URL string for this rule.
+  const std::string& GetPostServiceUrl() const {
+    return post_service_url_;
+  }
+
  private:
   std::string id_;
   std::vector<FormatElement> format_;
@@ -128,6 +138,8 @@
   int postal_code_name_message_id_;
   std::string name_;
   std::string latin_name_;
+  std::string postal_code_example_;
+  std::string post_service_url_;
 
   DISALLOW_COPY_AND_ASSIGN(Rule);
 };
diff --git a/cpp/test/localization_test.cc b/cpp/test/localization_test.cc
index 6be9d2c..e4d866c 100644
--- a/cpp/test/localization_test.cc
+++ b/cpp/test/localization_test.cc
@@ -14,6 +14,10 @@
 
 #include <libaddressinput/localization.h>
 
+#include <libaddressinput/address_data.h>
+#include <libaddressinput/address_field.h>
+#include <libaddressinput/address_problem.h>
+
 #include <string>
 
 #include <gtest/gtest.h>
@@ -23,8 +27,24 @@
 
 namespace {
 
+using i18n::addressinput::AddressData;
+using i18n::addressinput::AddressField;
+using i18n::addressinput::AddressProblem;
+using i18n::addressinput::ADMIN_AREA;
+using i18n::addressinput::COUNTRY;
+using i18n::addressinput::DEPENDENT_LOCALITY;
+using i18n::addressinput::INVALID_FORMAT;
 using i18n::addressinput::INVALID_MESSAGE_ID;
+using i18n::addressinput::LOCALITY;
 using i18n::addressinput::Localization;
+using i18n::addressinput::MISMATCHING_VALUE;
+using i18n::addressinput::MISSING_REQUIRED_FIELD;
+using i18n::addressinput::POSTAL_CODE;
+using i18n::addressinput::RECIPIENT;
+using i18n::addressinput::SORTING_CODE;
+using i18n::addressinput::STREET_ADDRESS;
+using i18n::addressinput::UNKNOWN_VALUE;
+using i18n::addressinput::USES_P_O_BOX;
 
 // Tests for Localization object.
 class LocalizationTest : public testing::TestWithParam<int> {
@@ -57,23 +77,41 @@
 // Tests all message identifiers.
 INSTANTIATE_TEST_CASE_P(
     AllMessages, LocalizationTest,
-    testing::Values(IDS_LIBADDRESSINPUT_COUNTRY_OR_REGION_LABEL,
-                    IDS_LIBADDRESSINPUT_LOCALITY_LABEL,
-                    IDS_LIBADDRESSINPUT_DISTRICT,
-                    IDS_LIBADDRESSINPUT_RECIPIENT_LABEL,
-                    IDS_LIBADDRESSINPUT_ADDRESS_LINE_1_LABEL,
-                    IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL,
-                    IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL,
-                    IDS_LIBADDRESSINPUT_AREA,
-                    IDS_LIBADDRESSINPUT_COUNTY,
-                    IDS_LIBADDRESSINPUT_DEPARTMENT,
-                    IDS_LIBADDRESSINPUT_DO_SI,
-                    IDS_LIBADDRESSINPUT_EMIRATE,
-                    IDS_LIBADDRESSINPUT_ISLAND,
-                    IDS_LIBADDRESSINPUT_PARISH,
-                    IDS_LIBADDRESSINPUT_PREFECTURE,
-                    IDS_LIBADDRESSINPUT_PROVINCE,
-                    IDS_LIBADDRESSINPUT_STATE));
+    testing::Values(
+        IDS_LIBADDRESSINPUT_COUNTRY_OR_REGION_LABEL,
+        IDS_LIBADDRESSINPUT_LOCALITY_LABEL,
+        IDS_LIBADDRESSINPUT_ADDRESS_LINE_1_LABEL,
+        IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL,
+        IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL,
+        IDS_LIBADDRESSINPUT_AREA,
+        IDS_LIBADDRESSINPUT_COUNTY,
+        IDS_LIBADDRESSINPUT_DEPARTMENT,
+        IDS_LIBADDRESSINPUT_DISTRICT,
+        IDS_LIBADDRESSINPUT_DO_SI,
+        IDS_LIBADDRESSINPUT_EMIRATE,
+        IDS_LIBADDRESSINPUT_ISLAND,
+        IDS_LIBADDRESSINPUT_PARISH,
+        IDS_LIBADDRESSINPUT_PREFECTURE,
+        IDS_LIBADDRESSINPUT_PROVINCE,
+        IDS_LIBADDRESSINPUT_STATE,
+        IDS_LIBADDRESSINPUT_RECIPIENT_LABEL,
+        IDS_LIBADDRESSINPUT_MISSING_REQUIRED_FIELD,
+        IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE_AND_URL,
+        IDS_LIBADDRESSINPUT_MISSING_REQUIRED_POSTAL_CODE_EXAMPLE,
+        IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE_AND_URL,
+        IDS_LIBADDRESSINPUT_MISSING_REQUIRED_ZIP_CODE_EXAMPLE,
+        IDS_LIBADDRESSINPUT_UNKNOWN_VALUE,
+        IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE_AND_URL,
+        IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE_EXAMPLE,
+        IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_POSTAL_CODE,
+        IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE_AND_URL,
+        IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP_CODE_EXAMPLE,
+        IDS_LIBADDRESSINPUT_UNRECOGNIZED_FORMAT_ZIP,
+        IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE_URL,
+        IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_POSTAL_CODE,
+        IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP_URL,
+        IDS_LIBADDRESSINPUT_MISMATCHING_VALUE_ZIP,
+        IDS_LIBADDRESSINPUT_PO_BOX_FORBIDDEN_VALUE));
 
 // Verifies that an invalid message identifier results in an empty string in the
 // default configuration.
@@ -86,4 +124,349 @@
   EXPECT_EQ("en", localization_.GetLanguage());
 }
 
+TEST(LocalizationGetErrorMessageTest, MissingRequiredPostalCode) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "CH";
+  EXPECT_EQ(std::string("You must provide a postal code, for example\n") +
+            "    2544,1211,1556,3030.\n" +
+            "    Don't know your postal code? Find it out\n" +
+            "    <a href=\"http://www.post.ch/db/owa/pv_plz_pack/pr_main\">" +
+            "here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISSING_REQUIRED_FIELD, true, true));
+  EXPECT_EQ(std::string("You must provide a postal code, for example\n") +
+            "    2544,1211,1556,3030.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISSING_REQUIRED_FIELD, true, false));
+  EXPECT_EQ("You can't leave this empty.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISSING_REQUIRED_FIELD, false, false));
+  EXPECT_EQ("You can't leave this empty.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISSING_REQUIRED_FIELD, false, true));
+}
+
+TEST(LocalizationGetErrorMessageTest, MissingRequiredZipCode) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "US";
+  EXPECT_EQ(std::string("You must provide a ZIP code, for example\n") +
+            "    95014,22162-1010.\n" +
+            "    Don't know your ZIP code? Find it out\n" +
+            "    <a href=\"https://tools.usps.com/go/ZipLookupAction!" +
+            "input.action\">here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISSING_REQUIRED_FIELD, true, true));
+  EXPECT_EQ(std::string("You must provide a ZIP code, for example\n") +
+            "    95014,22162-1010.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISSING_REQUIRED_FIELD, true, false));
+  EXPECT_EQ("You can't leave this empty.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISSING_REQUIRED_FIELD, false, false));
+  EXPECT_EQ("You can't leave this empty.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+            MISSING_REQUIRED_FIELD, false, true));
+}
+
+TEST(LocalizationGetErrorMessageTest, MissingRequiredOtherFields) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "US";
+  std::vector<AddressField> other_fields;
+  other_fields.push_back(COUNTRY);
+  other_fields.push_back(ADMIN_AREA);
+  other_fields.push_back(LOCALITY);
+  other_fields.push_back(DEPENDENT_LOCALITY);
+  other_fields.push_back(SORTING_CODE);
+  other_fields.push_back(STREET_ADDRESS);
+  other_fields.push_back(RECIPIENT);
+  for (std::vector<AddressField>::iterator it = other_fields.begin();
+       it != other_fields.end(); it++) {
+    EXPECT_EQ("You can't leave this empty.",
+              localization.GetErrorMessage(
+                  address, *it, MISSING_REQUIRED_FIELD, true, true));
+    EXPECT_EQ("You can't leave this empty.",
+              localization.GetErrorMessage(
+                  address, *it, MISSING_REQUIRED_FIELD, true, false));
+    EXPECT_EQ("You can't leave this empty.",
+              localization.GetErrorMessage(
+                  address, *it, MISSING_REQUIRED_FIELD, false, false));
+    EXPECT_EQ("You can't leave this empty.",
+              localization.GetErrorMessage(
+                  address, *it, MISSING_REQUIRED_FIELD, false, true));
+  }
+}
+
+TEST(LocalizationGetErrorMessageTest, UnknownValueOtherFields) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "US";
+  address.administrative_area = "bad admin area";
+  address.locality = "bad locality";
+  address.dependent_locality = "bad dependent locality";
+  address.sorting_code = "bad sorting code";
+  std::vector<std::string> address_line;
+  address_line.push_back("bad address line 1");
+  address_line.push_back("bad address line 2");
+  address.address_line = address_line;
+  address.recipient = "bad recipient";
+  EXPECT_EQ(std::string("US\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, COUNTRY, UNKNOWN_VALUE, true, true));
+  EXPECT_EQ(std::string("US\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, COUNTRY, UNKNOWN_VALUE, true, false));
+  EXPECT_EQ(std::string("US\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, COUNTRY, UNKNOWN_VALUE, false, false));
+  EXPECT_EQ(std::string("US\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, COUNTRY, UNKNOWN_VALUE, false, true));
+  EXPECT_EQ(std::string("bad admin area\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, ADMIN_AREA, UNKNOWN_VALUE, true, true));
+  EXPECT_EQ(std::string("bad admin area\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, ADMIN_AREA, UNKNOWN_VALUE, true, false));
+  EXPECT_EQ(std::string("bad admin area\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, ADMIN_AREA, UNKNOWN_VALUE, false, false));
+  EXPECT_EQ(std::string("bad admin area\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, ADMIN_AREA, UNKNOWN_VALUE, false, true));
+  EXPECT_EQ(std::string("bad locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, LOCALITY, UNKNOWN_VALUE, true, true));
+  EXPECT_EQ(std::string("bad locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, LOCALITY, UNKNOWN_VALUE, true, false));
+  EXPECT_EQ(std::string("bad locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, LOCALITY, UNKNOWN_VALUE, false, false));
+  EXPECT_EQ(std::string("bad locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, LOCALITY, UNKNOWN_VALUE, false, true));
+  EXPECT_EQ(std::string("bad dependent locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, DEPENDENT_LOCALITY, UNKNOWN_VALUE, true, true));
+  EXPECT_EQ(std::string("bad dependent locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, DEPENDENT_LOCALITY, UNKNOWN_VALUE, true, false));
+  EXPECT_EQ(std::string("bad dependent locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, DEPENDENT_LOCALITY, UNKNOWN_VALUE, false, false));
+  EXPECT_EQ(std::string("bad dependent locality\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, DEPENDENT_LOCALITY, UNKNOWN_VALUE, false, true));
+  EXPECT_EQ(std::string("bad sorting code\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, SORTING_CODE, UNKNOWN_VALUE, true, true));
+  EXPECT_EQ(std::string("bad sorting code\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, SORTING_CODE, UNKNOWN_VALUE, true, false));
+  EXPECT_EQ(std::string("bad sorting code\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, SORTING_CODE, UNKNOWN_VALUE, false, false));
+  EXPECT_EQ(std::string("bad sorting code\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, SORTING_CODE, UNKNOWN_VALUE, false, true));
+  EXPECT_EQ(std::string("bad address line 1\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, STREET_ADDRESS, UNKNOWN_VALUE, true, true));
+  EXPECT_EQ(std::string("bad address line 1\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, STREET_ADDRESS, UNKNOWN_VALUE, true, false));
+  EXPECT_EQ(std::string("bad address line 1\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, STREET_ADDRESS, UNKNOWN_VALUE, false, false));
+  EXPECT_EQ(std::string("bad address line 1\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, STREET_ADDRESS, UNKNOWN_VALUE, false, true));
+  EXPECT_EQ(std::string("bad recipient\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, RECIPIENT, UNKNOWN_VALUE, true, true));
+  EXPECT_EQ(std::string("bad recipient\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, RECIPIENT, UNKNOWN_VALUE, true, false));
+  EXPECT_EQ(std::string("bad recipient\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, RECIPIENT, UNKNOWN_VALUE, false, false));
+  EXPECT_EQ(std::string("bad recipient\n    ") +
+            "is not recognized as a known value for this field.",
+            localization.GetErrorMessage(
+                address, RECIPIENT, UNKNOWN_VALUE, false, true));
+}
+
+TEST(LocalizationGetErrorMessageTest, InvalidFormatPostalCode) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "CH";
+  EXPECT_EQ(std::string("This postal code format is not recognized. Example ") +
+            "of a valid postal code:\n" +
+            "    2544,1211,1556,3030.\n" +
+            "    Don't know your postal code? Find it out\n" +
+            "    <a href=\"http://www.post.ch/db/owa/pv_plz_pack/pr_main\">" +
+            "here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, true, true));
+  EXPECT_EQ(std::string("This postal code format is not recognized. Example ") +
+            "of a valid postal code:\n" +
+            "    2544,1211,1556,3030.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, true, false));
+  EXPECT_EQ("This postal code format is not recognized.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, false, false));
+  EXPECT_EQ("This postal code format is not recognized.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, false, true));
+}
+
+TEST(LocalizationGetErrorMessageTest, InvalidFormatZipCode) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "US";
+  EXPECT_EQ(std::string("This ZIP code format is not recognized. Example of ") +
+            "a valid ZIP code:\n" +
+            "    95014,22162-1010.\n" +
+            "    Don't know your ZIP code? Find it out\n" +
+            "    <a href=\"https://tools.usps.com/go/ZipLookupAction!" +
+            "input.action\">here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, true, true));
+  EXPECT_EQ(std::string("This ZIP code format is not recognized. Example of ") +
+            "a valid ZIP code:\n" +
+            "    95014,22162-1010.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, true, false));
+  EXPECT_EQ("This ZIP code format is not recognized.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, false, false));
+  EXPECT_EQ("This ZIP code format is not recognized.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         INVALID_FORMAT, false, true));
+}
+
+TEST(LocalizationGetErrorMessageTest, MismatchingValuePostalCode) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "CH";
+  EXPECT_EQ(std::string("This postal code does not appear to match the rest ") +
+            "of this address.\n" +
+            "    Don't know your postal code? Find it out\n" +
+            "    <a href=\"http://www.post.ch/db/owa/pv_plz_pack/pr_main\">" +
+            "here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, true, true));
+  EXPECT_EQ(std::string("This postal code does not appear to match the rest ") +
+            "of this address.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, true, false));
+  EXPECT_EQ(std::string("This postal code does not appear to match the rest ") +
+            "of this address.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, false, false));
+  EXPECT_EQ(std::string("This postal code does not appear to match the rest ") +
+            "of this address.\n" +
+            "    Don't know your postal code? Find it out\n" +
+            "    <a href=\"http://www.post.ch/db/owa/pv_plz_pack/pr_main\">" +
+            "here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, false, true));
+}
+
+TEST(LocalizationGetErrorMessageTest, MismatchingValueZipCode) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "US";
+  EXPECT_EQ(std::string("This ZIP code does not appear to match the rest of ") +
+            "this address.\n" +
+            "    Don't know your ZIP code? Find it out\n" +
+            "    <a href=\"https://tools.usps.com/go/ZipLookupAction!" +
+            "input.action\">here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, true, true));
+  EXPECT_EQ(std::string("This ZIP code does not appear to match the rest of ") +
+            "this address.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, true, false));
+  EXPECT_EQ(std::string("This ZIP code does not appear to match the rest of ") +
+            "this address.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, false, false));
+  EXPECT_EQ(std::string("This ZIP code does not appear to match the rest of ") +
+            "this address.\n" +
+            "    Don't know your ZIP code? Find it out\n" +
+            "    <a href=\"https://tools.usps.com/go/ZipLookupAction!" +
+            "input.action\">here</a>.",
+            localization.GetErrorMessage(address, POSTAL_CODE,
+                                         MISMATCHING_VALUE, false, true));
+}
+
+TEST(LocalizationGetErrorMessageTest, UsesPOBoxOtherFields) {
+  Localization localization;
+  AddressData address;
+  address.region_code = "US";
+  std::vector<AddressField> other_fields;
+  other_fields.push_back(COUNTRY);
+  other_fields.push_back(ADMIN_AREA);
+  other_fields.push_back(LOCALITY);
+  other_fields.push_back(DEPENDENT_LOCALITY);
+  other_fields.push_back(SORTING_CODE);
+  other_fields.push_back(STREET_ADDRESS);
+  other_fields.push_back(RECIPIENT);
+  for (std::vector<AddressField>::iterator it = other_fields.begin();
+       it != other_fields.end(); it++) {
+    EXPECT_EQ(std::string("This address line appears to contain a post ") +
+              "office box. Please use a street\n" +
+              "    or building address.",
+              localization.GetErrorMessage(
+                  address, *it, USES_P_O_BOX, true, true));
+    EXPECT_EQ(std::string("This address line appears to contain a post ") +
+              "office box. Please use a street\n" +
+              "    or building address.",
+              localization.GetErrorMessage(
+                  address, *it, USES_P_O_BOX, true, false));
+    EXPECT_EQ(std::string("This address line appears to contain a post ") +
+              "office box. Please use a street\n" +
+              "    or building address.",
+              localization.GetErrorMessage(
+                  address, *it, USES_P_O_BOX, false, false));
+    EXPECT_EQ(std::string("This address line appears to contain a post ") +
+              "office box. Please use a street\n" +
+              "    or building address.",
+              localization.GetErrorMessage(
+                  address, *it, USES_P_O_BOX, false, true));
+  }
+}
+
 }  // namespace
diff --git a/cpp/test/rule_test.cc b/cpp/test/rule_test.cc
index 5fdab33..38ac77b 100644
--- a/cpp/test/rule_test.cc
+++ b/cpp/test/rule_test.cc
@@ -57,7 +57,9 @@
                                        "\"languages\":\"en~fr\","
                                        "\"zip\":\"\\\\d{3}\","
                                        "\"state_name_type\":\"area\","
-                                       "\"zip_name_type\":\"postal\""
+                                       "\"zip_name_type\":\"postal\","
+                                       "\"zipex\":\"1234\","
+                                       "\"posturl\":\"http://www.testpost.com\""
                                        "}"));
 
   Rule copy;
@@ -73,6 +75,8 @@
             copy.GetPostalCodeNameMessageId());
   EXPECT_NE(rule.GetName(), copy.GetName());
   EXPECT_NE(rule.GetLatinName(), copy.GetLatinName());
+  EXPECT_NE(rule.GetPostalCodeExample(), copy.GetPostalCodeExample());
+  EXPECT_NE(rule.GetPostServiceUrl(), copy.GetPostServiceUrl());
 
   EXPECT_TRUE(rule.GetPostalCodeMatcher() != NULL);
   EXPECT_TRUE(copy.GetPostalCodeMatcher() == NULL);
@@ -90,6 +94,8 @@
             copy.GetPostalCodeNameMessageId());
   EXPECT_EQ(rule.GetName(), copy.GetName());
   EXPECT_EQ(rule.GetLatinName(), copy.GetLatinName());
+  EXPECT_EQ(rule.GetPostalCodeExample(), copy.GetPostalCodeExample());
+  EXPECT_EQ(rule.GetPostServiceUrl(), copy.GetPostServiceUrl());
 
   EXPECT_TRUE(copy.GetPostalCodeMatcher() != NULL);
 }
@@ -99,24 +105,32 @@
   ASSERT_TRUE(rule.ParseSerializedRule("{"
                                        "\"fmt\":\"%S%Z\","
                                        "\"state_name_type\":\"area\","
-                                       "\"zip_name_type\":\"postal\""
+                                       "\"zip_name_type\":\"postal\","
+                                       "\"zipex\":\"1234\","
+                                       "\"posturl\":\"http://www.testpost.com\""
                                        "}"));
   EXPECT_FALSE(rule.GetFormat().empty());
   EXPECT_EQ(IDS_LIBADDRESSINPUT_AREA,
             rule.GetAdminAreaNameMessageId());
   EXPECT_EQ(IDS_LIBADDRESSINPUT_POSTAL_CODE_LABEL,
             rule.GetPostalCodeNameMessageId());
+  EXPECT_EQ("1234", rule.GetPostalCodeExample());
+  EXPECT_EQ("http://www.testpost.com", rule.GetPostServiceUrl());
 
   ASSERT_TRUE(rule.ParseSerializedRule("{"
                                        "\"fmt\":\"\","
                                        "\"state_name_type\":\"do_si\","
-                                       "\"zip_name_type\":\"zip\""
+                                       "\"zip_name_type\":\"zip\","
+                                       "\"zipex\":\"5678\","
+                                       "\"posturl\":\"http://www.fakepost.com\""
                                        "}"));
   EXPECT_TRUE(rule.GetFormat().empty());
   EXPECT_EQ(IDS_LIBADDRESSINPUT_DO_SI,
             rule.GetAdminAreaNameMessageId());
   EXPECT_EQ(IDS_LIBADDRESSINPUT_ZIP_CODE_LABEL,
             rule.GetPostalCodeNameMessageId());
+  EXPECT_EQ("5678", rule.GetPostalCodeExample());
+  EXPECT_EQ("http://www.fakepost.com", rule.GetPostServiceUrl());
 }
 
 TEST(RuleTest, ParsesFormatCorrectly) {
@@ -178,6 +192,19 @@
   EXPECT_EQ(expected, rule.GetLanguages());
 }
 
+TEST(RuleTest, ParsesPostalCodeExampleCorrectly) {
+  Rule rule;
+  ASSERT_TRUE(rule.ParseSerializedRule("{\"zipex\":\"1234,12345-6789\"}"));
+  EXPECT_EQ("1234,12345-6789", rule.GetPostalCodeExample());
+}
+
+TEST(RuleTest, ParsesPostServiceUrlCorrectly) {
+  Rule rule;
+  ASSERT_TRUE(
+      rule.ParseSerializedRule("{\"posturl\":\"http://www.testpost.com\"}"));
+  EXPECT_EQ("http://www.testpost.com", rule.GetPostServiceUrl());
+}
+
 TEST(RuleTest, PostalCodeMatcher) {
   Rule rule;
   ASSERT_TRUE(rule.ParseSerializedRule("{\"zip\":\"\\\\d{3}\"}"));