Add better unit tests and imbue C locale in floating point to_string

ToStringTest.cpp is a better place than JSONProtoTest.cpp for to_string
tests. Move global locale-related unit tests there.
Also imbue the C locale in the floating point to_string functions to avoid
decimal number strings formatted with comma instead of decimal point.
In Dockerfiles, install de_DE locale because it uses decimal comma.
diff --git a/build/docker/ubuntu-bionic/Dockerfile b/build/docker/ubuntu-bionic/Dockerfile
index 0407a6f..a11e9ba 100644
--- a/build/docker/ubuntu-bionic/Dockerfile
+++ b/build/docker/ubuntu-bionic/Dockerfile
@@ -258,6 +258,7 @@
 `# Locale dependencies` \
       locales && \
     locale-gen en_US.UTF-8 && \
+    locale-gen de_DE.UTF-8 && \
     update-locale
 
 # cppcheck-1.82 has a nasty cpp parser bug, so we're using something newer
diff --git a/build/docker/ubuntu-disco/Dockerfile b/build/docker/ubuntu-disco/Dockerfile
index a175ed5..de99574 100644
--- a/build/docker/ubuntu-disco/Dockerfile
+++ b/build/docker/ubuntu-disco/Dockerfile
@@ -260,6 +260,7 @@
 `# Locale dependencies` \
       locales && \
     locale-gen en_US.UTF-8 && \
+    locale-gen de_DE.UTF-8 && \
     update-locale
 
 # cppcheck-1.82 has a nasty cpp parser bug, so we're using something newer
diff --git a/build/docker/ubuntu-xenial/Dockerfile b/build/docker/ubuntu-xenial/Dockerfile
index 1c4373e..441b692 100644
--- a/build/docker/ubuntu-xenial/Dockerfile
+++ b/build/docker/ubuntu-xenial/Dockerfile
@@ -240,6 +240,7 @@
 `# Locale dependencies` \
       locales && \
     locale-gen en_US.UTF-8 && \
+    locale-gen de_DE.UTF-8 && \
     update-locale
 
 # Clean up
diff --git a/lib/cpp/src/thrift/TToString.h b/lib/cpp/src/thrift/TToString.h
index fbeac51..79743fd 100644
--- a/lib/cpp/src/thrift/TToString.h
+++ b/lib/cpp/src/thrift/TToString.h
@@ -44,6 +44,7 @@
 // is enabled.
 inline std::string to_string(const float& t) {
   std::ostringstream o;
+  o.imbue(std::locale("C"));
   o.precision(static_cast<std::streamsize>(std::ceil(static_cast<double>(std::numeric_limits<float>::digits * std::log10(2.0f) + 1))));
   o << t;
   return o.str();
@@ -51,6 +52,7 @@
 
 inline std::string to_string(const double& t) {
   std::ostringstream o;
+  o.imbue(std::locale("C"));
   o.precision(static_cast<std::streamsize>(std::ceil(static_cast<double>(std::numeric_limits<double>::digits * std::log10(2.0f) + 1))));
   o << t;
   return o.str();
@@ -58,6 +60,7 @@
 
 inline std::string to_string(const long double& t) {
   std::ostringstream o;
+  o.imbue(std::locale("C"));
   o.precision(static_cast<std::streamsize>(std::ceil(static_cast<double>(std::numeric_limits<long double>::digits * std::log10(2.0f) + 1))));
   o << t;
   return o.str();
diff --git a/lib/cpp/test/JSONProtoTest.cpp b/lib/cpp/test/JSONProtoTest.cpp
index 6d1cd8c..082c8a2 100644
--- a/lib/cpp/test/JSONProtoTest.cpp
+++ b/lib/cpp/test/JSONProtoTest.cpp
@@ -22,7 +22,6 @@
 #include <iomanip>
 #include <sstream>
 #include <thrift/protocol/TJSONProtocol.h>
-#include <locale>
 #include <memory>
 #include <thrift/transport/TBufferTransports.h>
 #include "gen-cpp/DebugProtoTest_types.h"
@@ -351,26 +350,3 @@
   BOOST_CHECK_THROW(ooe2.read(proto.get()),
     apache::thrift::protocol::TProtocolException);
 }
-
-BOOST_AUTO_TEST_CASE(test_json_locale_with_thousands_separator_comma) {
-  testCaseSetup_1();
-
-  const std::string expected_result(
-  "{\"1\":{\"tf\":1},\"2\":{\"tf\":0},\"3\":{\"i8\":127},\"4\":{\"i16\":27000},"
-  "\"5\":{\"i32\":16777216},\"6\":{\"i64\":6000000000},\"7\":{\"dbl\":3.1415926"
-  "535897931},\"8\":{\"str\":\"JSON THIS! \\\"\\u0001\"},\"9\":{\"str\":\"\xd7\\"
-  "n\\u0007\\t\"},\"10\":{\"tf\":0},\"11\":{\"str\":\"AQIDrQ\"},\"12\":{\"lst\""
-  ":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2,3]},\"14\":{\"lst\":[\"i64"
-  "\",3,1,2,3]}}");
-
-#if _WIN32
-  std::locale::global(std::locale("en-US.UTF-8"));
-#else
-  std::locale::global(std::locale("en_US.UTF-8"));
-#endif
-
-  const std::string result(apache::thrift::ThriftJSONString(*ooe));
-
-  BOOST_CHECK_MESSAGE(!expected_result.compare(result),
-    "Expected:\n" << expected_result << "\nGotten:\n" << result);
-}
diff --git a/lib/cpp/test/ToStringTest.cpp b/lib/cpp/test/ToStringTest.cpp
index 5a05ed7..722e590 100644
--- a/lib/cpp/test/ToStringTest.cpp
+++ b/lib/cpp/test/ToStringTest.cpp
@@ -19,6 +19,7 @@
 
 #include <vector>
 #include <map>
+#include <locale>
 
 #include <boost/test/unit_test.hpp>
 
@@ -40,6 +41,26 @@
   BOOST_CHECK_EQUAL(to_string("abc"), "abc");
 }
 
+BOOST_AUTO_TEST_CASE(locale_en_US_int_to_string) {
+#if _WIN32
+  std::locale::global(std::locale("en-US.UTF-8"));
+#else
+  std::locale::global(std::locale("en_US.UTF-8"));
+#endif
+  BOOST_CHECK_EQUAL(to_string(1000000), "1000000");
+}
+
+BOOST_AUTO_TEST_CASE(locale_de_DE_floating_point_to_string) {
+#if _WIN32
+  std::locale::global(std::locale("de-DE.UTF-8"));
+#else
+  std::locale::global(std::locale("de_DE.UTF-8"));
+#endif
+  BOOST_CHECK_EQUAL(to_string(1.5), "1.5");
+  BOOST_CHECK_EQUAL(to_string(1.5f), "1.5");
+  BOOST_CHECK_EQUAL(to_string(1.5L), "1.5");
+}
+
 BOOST_AUTO_TEST_CASE(empty_vector_to_string) {
   std::vector<int> l;
   BOOST_CHECK_EQUAL(to_string(l), "[]");