Mostly CFormat-related code cleanup (things spotted during forward porting into FM3)
diff --git a/src/main/java/freemarker/core/ArithmeticEngine.java b/src/main/java/freemarker/core/ArithmeticEngine.java
index 468f5e4..dc946d2 100644
--- a/src/main/java/freemarker/core/ArithmeticEngine.java
+++ b/src/main/java/freemarker/core/ArithmeticEngine.java
@@ -116,7 +116,7 @@
         @Override
         public int compareNumbers(Number first, Number second) {
             // We try to find the result based on the sign (+/-/0) first, because:
-            // - It's much faster than converting to BigDecial, and comparing to 0 is the most common comparison.
+            // - It's much faster than converting to BigDecimal, and comparing to 0 is the most common comparison.
             // - It doesn't require any type conversions, and thus things like "Infinity > 0" won't fail.
             int firstSignum = NumberUtil.getSignum(first); 
             int secondSignum = NumberUtil.getSignum(second);
diff --git a/src/main/java/freemarker/core/CTemplateNumberFormat.java b/src/main/java/freemarker/core/CTemplateNumberFormat.java
index dc662b7..3df4177 100644
--- a/src/main/java/freemarker/core/CTemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/CTemplateNumberFormat.java
@@ -136,11 +136,10 @@
             BigDecimal bd = ((BigDecimal) num).stripTrailingZeros();
             int scale = bd.scale();
             if (scale <= 0) {
-                // A whole number. Myabe a long ID in a database or other system, and for those exponential form is not
+                // A whole number. Maybe a long ID in a database or other system, and for those exponential form is not
                 // expected generally, so we avoid that. But then, it becomes too easy to write something like
                 // 1e1000000000000 and kill the server with a terra byte long rendering of the number, so for lengths
-                // that
-                // realistically aren't ID-s or such, we use exponential format after all:
+                // that realistically aren't ID-s or such, we use exponential format after all:
                 if (scale <= -100) {
                     return bd.toString(); // Will give exponential form for this scale
                 }
@@ -196,5 +195,4 @@
         return "c";
     }
 
-
 }
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 0e03b22..a70e247 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -66,6 +66,7 @@
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
 import freemarker.template._VersionInts;
+import freemarker.template.utility.CollectionUtils;
 import freemarker.template.utility.NullArgumentException;
 import freemarker.template.utility.StringUtil;
 
@@ -82,7 +83,7 @@
  * object.
  */
 public class Configurable {
-    static final String C_TRUE_FALSE = "true,false";
+    static final String BOOLEAN_FORMAT_LEGACY_DEFAULT = "true,false";
     static final String C_FORMAT_STRING = "c";
 
     private static final String NULL = "null";
@@ -386,8 +387,6 @@
     private TimeZone sqlDataAndTimeTimeZone;
     private boolean sqlDataAndTimeTimeZoneSet;
     private String booleanFormat;
-    private String booleanFormatCommaSplitTrueSide;  // deduced from booleanFormat
-    private String booleanFormatCommaSplitFalseSide;  // deduced from booleanFormat
     private Integer classicCompatible;
     private TemplateExceptionHandler templateExceptionHandler;
     private AttemptExceptionReporter attemptExceptionReporter;
@@ -493,7 +492,7 @@
         // outputEncoding and urlEscapingCharset defaults to null,
         // which means "not specified"
 
-        setBooleanFormat(C_TRUE_FALSE);
+        setBooleanFormat(BOOLEAN_FORMAT_LEGACY_DEFAULT);
         
         customAttributes = new HashMap();
         
@@ -697,12 +696,13 @@
     }
 
     /**
-     * Sets the format (usually a computer language) used for the {@code c}, {@code cn} built-ins, and for the
+     * Sets the format (usually a computer language) used for {@code ?c}, {@code ?cn}, and for the
      * {@code "c"} ({@code "computer"} before 2.3.32) {@link #setNumberFormat(String) number_format}, and the
      * {@code "c"} {@link #setBooleanFormat(String) boolean_format}.
      *
      * <p>The default value depends on {@link Configuration#Configuration(Version) incompatible_improvements}.
-     * If that's 2.3.32 or higher, then it's {@code "JavaScript or JSON"}, otherwise it's {@code "legacy"}.
+     * If that's 2.3.32 or higher, then it's {@link JavaScriptOrJSONCFormat#INSTANCE "JavaScript or JSON"},
+     * otherwise it's {@link LegacyCFormat#INSTANCE "legacy"}.
      *
      * @since 2.3.32
      */
@@ -863,7 +863,7 @@
      *   <li>{@code "c"} (recognized since 2.3.32): The number format used by FTL's {@code c} built-in (like in
      *       {@code someNumber?c}). So with this <code>${someNumber}</code> will output the same as
      *       <code>${someNumber?c}</code>. This should only be used if the template solely generates source code,
-     *       configuration file, or other content that's nor read by normal users. If the template contains parts that's
+     *       configuration file, or other content that's not read by normal users. If the template contains parts that's
      *       read by normal users (like typical a web page), you are not supposed to use this.</li>
      *   <li>{@code "computer"}: The old (deprecated) name for {@code "c"}. Recognized by all FreeMarker versions.</li>
      *   <li>{@code "currency"}: The number format returned by {@link NumberFormat#getCurrencyInstance(Locale)}</li>
@@ -882,8 +882,7 @@
      *       <code><i>parameters</i></code> is parsed by the custom {@link TemplateNumberFormat}.
      *   </li>
      * </ul>
-     * 
-     *   
+     *
      * <p>Defaults to <tt>"number"</tt>.
      */
     public void setNumberFormat(String numberFormat) {
@@ -1034,11 +1033,38 @@
      * only influenced the result of {@code myBool?string}. 
      */
     public void setBooleanFormat(String booleanFormat) {
+        validateBooleanFormat(booleanFormat);
+        this.booleanFormat = booleanFormat;
+        properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat);
+    }
+
+    /**
+     * @throws IllegalArgumentException If the format string has unrecognized format
+     */
+    private static void validateBooleanFormat(String booleanFormat) {
+        parseOrValidateBooleanFormat(booleanFormat, true);
+    }
+
+    /**
+     * @return {@code null} for legacy default (not set in effect), empty array if the {@link CFormat} should be used,
+     * and an array of {@code [trueString, falseString]} otherwise.
+     *
+     * @throws IllegalArgumentException If the format string has unrecognized format
+     */
+    static String[] parseBooleanFormat(String booleanFormat) {
+        return parseOrValidateBooleanFormat(booleanFormat, false);
+    }
+
+    private static String[] parseOrValidateBooleanFormat(String booleanFormat, boolean validateOnly) {
         NullArgumentException.check("booleanFormat", booleanFormat);
 
-        if (booleanFormat.equals(C_TRUE_FALSE) || booleanFormat.equals(C_FORMAT_STRING)) {
-            booleanFormatCommaSplitTrueSide = null;
-            booleanFormatCommaSplitFalseSide = null;
+        if (booleanFormat.equals(C_FORMAT_STRING)) {
+            if (validateOnly) {
+                return null;
+            }
+            return CollectionUtils.EMPTY_STRING_ARRAY;
+        } else if (booleanFormat.equals(BOOLEAN_FORMAT_LEGACY_DEFAULT)) {
+            return null;
         } else {
             int commaIdx = booleanFormat.indexOf(',');
             if (commaIdx == -1) {
@@ -1047,12 +1073,14 @@
                                 "or it must be \"" + C_FORMAT_STRING + "\", but it was " +
                                 StringUtil.jQuote(booleanFormat) + ".");
             }
-            booleanFormatCommaSplitTrueSide = booleanFormat.substring(0, commaIdx);
-            booleanFormatCommaSplitFalseSide = booleanFormat.substring(commaIdx + 1);
+            if (validateOnly) {
+                return null;
+            }
+            return new String[] {
+                    booleanFormat.substring(0, commaIdx),
+                    booleanFormat.substring(commaIdx + 1)
+            };
         }
-
-        this.booleanFormat = booleanFormat;
-        properties.setProperty(BOOLEAN_FORMAT_KEY, booleanFormat);
     }
     
     /**
@@ -1063,32 +1091,6 @@
     }
 
     /**
-     * Non-{@code null} if the {@link #setBooleanFormat(String)} boolean_format} setting is comma separated true, and
-     * false strings.
-     *
-     * @since 2.3.32
-     */
-    String getBooleanFormatCommaSplitTrueSide() {
-        if (booleanFormat != null) {
-            return booleanFormatCommaSplitTrueSide;
-        }
-        return parent != null ? parent.getBooleanFormatCommaSplitTrueSide() : null;
-    }
-
-    /**
-     * Non-{@code null} if the {@link #setBooleanFormat(String)} boolean_format} setting is comma separated true, and
-     * false strings.
-     *
-     * @since 2.3.32
-     */
-    String getBooleanFormatCommaSplitFalseSide() {
-        if (booleanFormat != null) {
-            return booleanFormatCommaSplitFalseSide;
-        }
-        return parent != null ? parent.getBooleanFormatCommaSplitFalseSide() : null;
-    }
-
-    /**
      * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
      *  
      * @since 2.3.24
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index a56d9dd..0bd264b 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1691,9 +1691,9 @@
     }
 
     /**
-     * Returns the {@link TemplateNumberFormat} used for the <tt>c</tt> built-in currently uses in this environment.
+     * Returns the {@link TemplateNumberFormat} that {@code ?c}/{@code ?cn} uses.
      * Calling this method for many times is fine, as it internally caches the result object.
-     * Remember that {@link TemplateNumberFormat}-s are not thread-safe objects, so the resulting object should only
+     * Remember that {@link TemplateNumberFormat}-s aren't thread-safe objects, so the resulting object should only
      * be used in the same thread where this {@link Environment} runs.
      *
      * @since 2.3.32
@@ -1729,6 +1729,7 @@
         if (prevCFormat != cFormat) {
             cTemplateNumberFormat = null;
             cTemplateNumberFormatWithPre2331IcIBug = null;
+            cNumberFormat = null;
             if (cachedTemplateNumberFormats != null) {
                 cachedTemplateNumberFormats.remove(C_FORMAT_STRING);
                 cachedTemplateNumberFormats.remove(COMPUTER_FORMAT_STRING);
@@ -1812,7 +1813,7 @@
         return new _ErrorDescriptionBuilder(
                 "Can't convert boolean to string automatically, because the \"", BOOLEAN_FORMAT_KEY ,"\" setting was ",
                 new _DelayedJQuote(getBooleanFormat()),
-                (getBooleanFormat().equals(C_TRUE_FALSE)
+                (getBooleanFormat().equals(BOOLEAN_FORMAT_LEGACY_DEFAULT)
                         ? ", which is the legacy deprecated default, and we treat it as if no format was set. "
                         + "This is the default configuration; you should provide the format explicitly for each "
                         + "place where you print a boolean."
@@ -1869,17 +1870,19 @@
     }
 
     private void cacheTrueAndFalseStrings() {
-        String spitTrueSide = getBooleanFormatCommaSplitTrueSide();
-        if (spitTrueSide != null) {
-            cachedTrueString = spitTrueSide;
-            cachedFalseString = getBooleanFormatCommaSplitFalseSide();
-        } else if (getBooleanFormat().equals(C_FORMAT_STRING)) {
-            CFormat cFormat = getCFormat();
-            cachedTrueString = cFormat.getTrueString();
-            cachedFalseString = cFormat.getFalseString();
+        String[] parsedBooleanFormat = parseBooleanFormat(getBooleanFormat());
+        if (parsedBooleanFormat != null) {
+            if (parsedBooleanFormat.length == 0) {
+                CFormat cFormat = getCFormat();
+                cachedTrueString = cFormat.getTrueString();
+                cachedFalseString = cFormat.getFalseString();
+            } else {
+                cachedTrueString = parsedBooleanFormat[0];
+                cachedFalseString = parsedBooleanFormat[1];
+            }
         } else {
-            // This happens for C_TRUE_FALSE deliberately. That's the default for BC, but it's not a good default for human
-            // audience formatting, so we pretend that it wasn't set.
+            // This happens for BOOLEAN_FORMAT_LEGACY_DEFAULT deliberately. That's the default for BC, but it's not a
+            // good default for human audience formatting, so we pretend that it wasn't set.
             cachedTrueString = null;
             cachedFalseString = null;
         }
@@ -2410,7 +2413,7 @@
     }
 
     /**
-     * Similar to {@link #getLocalVariable(String)}, but might returns {@link TemplateNullModel}. Only used internally,
+     * Similar to {@link #getLocalVariable(String)}, but might return {@link TemplateNullModel}. Only used internally,
      * as {@link TemplateNullModel} is internal.
      *
      * @since 2.3.29
diff --git a/src/main/java/freemarker/core/MarkupOutputFormat.java b/src/main/java/freemarker/core/MarkupOutputFormat.java
index e8b3d96..c947c93 100644
--- a/src/main/java/freemarker/core/MarkupOutputFormat.java
+++ b/src/main/java/freemarker/core/MarkupOutputFormat.java
@@ -91,7 +91,7 @@
 
     /**
      * Returns the content as markup text; never {@code null}. If this {@link TemplateMarkupOutputModel} was created
-     * with {@link #fromMarkup(String)}, it might returns the original markup text literally, but this is not required
+     * with {@link #fromMarkup(String)}, it might return the original markup text literally, but this is not required
      * as far as the returned markup means the same. If this {@link TemplateMarkupOutputModel} wasn't created
      * with {@link #fromMarkup(String)} and it doesn't yet have the markup, it has to generate the markup now.
      */
diff --git a/src/main/java/freemarker/core/StandardCFormats.java b/src/main/java/freemarker/core/StandardCFormats.java
new file mode 100644
index 0000000..aa56ccb
--- /dev/null
+++ b/src/main/java/freemarker/core/StandardCFormats.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 freemarker.core;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+final class StandardCFormats {
+    private StandardCFormats() {
+    }
+
+    static final Map<String, CFormat> STANDARD_C_FORMATS;
+    static {
+        Map<String, CFormat> map = new LinkedHashMap<>();
+        addStandardCFormat(map, JavaScriptOrJSONCFormat.INSTANCE);
+        addStandardCFormat(map, JSONCFormat.INSTANCE);
+        addStandardCFormat(map, JavaScriptCFormat.INSTANCE);
+        addStandardCFormat(map, JavaCFormat.INSTANCE);
+        addStandardCFormat(map, XSCFormat.INSTANCE);
+        addStandardCFormat(map, LegacyCFormat.INSTANCE);
+        STANDARD_C_FORMATS = Collections.unmodifiableMap(map);
+    }
+
+    private static void addStandardCFormat(Map<String, CFormat> map, CFormat cFormat) {
+        map.put(cFormat.getName(), cFormat);
+    }
+
+}
diff --git a/src/main/java/freemarker/core/_StandardCLanguages.java b/src/main/java/freemarker/core/_StandardCLanguages.java
deleted file mode 100644
index 9a22e7c..0000000
--- a/src/main/java/freemarker/core/_StandardCLanguages.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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 freemarker.core;
-
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-final class StandardCFormats {
-    private StandardCFormats() {
-    }
-
-    static final Map<String, CFormat> STANDARD_C_FORMATS = new LinkedHashMap<>();
-    static {
-        addStandardCFormat(JavaScriptOrJSONCFormat.INSTANCE);
-        addStandardCFormat(JSONCFormat.INSTANCE);
-        addStandardCFormat(JavaScriptCFormat.INSTANCE);
-        addStandardCFormat(JavaCFormat.INSTANCE);
-        addStandardCFormat(XSCFormat.INSTANCE);
-        addStandardCFormat(LegacyCFormat.INSTANCE);
-    }
-
-    private static void addStandardCFormat(CFormat cFormat) {
-        STANDARD_C_FORMATS.put(cFormat.getName(), cFormat);
-    }
-
-}
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index 5ac4ea1..47eb5c7 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -152,7 +152,7 @@
     /**
      * {@link String} class name to {@link StaticModel} cache.
      * This object only belongs to a single {@link BeansWrapper}.
-     * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
+     * This has to be final as {@link #getStaticModels()} might return it any time and then it has to remain a good
      * reference.
      */
     private final StaticModels staticModels;
@@ -160,7 +160,7 @@
     /**
      * {@link String} class name to {@link EnumerationModel} cache.
      * This object only belongs to a single {@link BeansWrapper}.
-     * This has to be final as {@link #getStaticModels()} might returns it any time and then it has to remain a good
+     * This has to be final as {@link #getStaticModels()} might return it any time and then it has to remain a good
      * reference.
      */
     private final ClassBasedModelFactory enumModels;
@@ -260,7 +260,7 @@
      *   </ul>
      *   
      *   <p>Note that the version will be normalized to the lowest version where the same incompatible
-     *   {@link BeansWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might returns
+     *   {@link BeansWrapper} improvements were already present, so {@link #getIncompatibleImprovements()} might return
      *   a lower version than what you have specified.
      *
      * @since 2.3.21
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index ff43033..5daaa90 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -63,7 +63,7 @@
      *            See the corresponding parameter of {@link BeansWrapper#BeansWrapper(Version)}. Not {@code null}. Note
      *            that the version will be normalized to the lowest version where the same incompatible
      *            {@link BeansWrapper} improvements were already present, so for the returned instance
-     *            {@link #getIncompatibleImprovements()} might returns a lower version than what you have specified
+     *            {@link #getIncompatibleImprovements()} might return a lower version than what you have specified
      *            here.
      * @param isIncompImprsAlreadyNormalized
      *            Tells if the {@code incompatibleImprovements} parameter contains an <em>already normalized</em> value.
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index 0f7f23b..8fa3250 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -41,7 +41,7 @@
  */
 public class _TemplateAPI {
     // ATTENTION! Don't refer to other classes in the static initializer of this class! Fields that need that must be
-    // moved into separate class, as it was done in _VersionInts, and _ObjectWrappers, to avoid class init deadlocks.
+    // moved into a separate class, to avoid class init deadlocks.
 
     public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) {
         NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
diff --git a/src/main/java/freemarker/template/utility/CollectionUtils.java b/src/main/java/freemarker/template/utility/CollectionUtils.java
index 814fd1f..a164286 100644
--- a/src/main/java/freemarker/template/utility/CollectionUtils.java
+++ b/src/main/java/freemarker/template/utility/CollectionUtils.java
@@ -26,6 +26,8 @@
 
     public static final Object[] EMPTY_OBJECT_ARRAY = new Object[] { };
 
+    public static final String[] EMPTY_STRING_ARRAY = new String[] { };
+
     public static final Class[] EMPTY_CLASS_ARRAY = new Class[] { };
 
     /**
diff --git a/src/main/java/freemarker/template/utility/StringUtil.java b/src/main/java/freemarker/template/utility/StringUtil.java
index 51f22a7..6d5f3fa 100644
--- a/src/main/java/freemarker/template/utility/StringUtil.java
+++ b/src/main/java/freemarker/template/utility/StringUtil.java
@@ -933,7 +933,7 @@
             } else {
                 b.append(c);
             }
-        } // for each characters
+        } // for each character
         b.append('"');
         return b.toString();
     }
@@ -1316,24 +1316,26 @@
                     c = s.charAt(i);
                 }
             } // if has to be escaped
-        } // for each characters
+        } // for each character
         return quote ? '"' + s + '"' : s;
     }
     
     /**
      * Escapes a {@link String} to be safely insertable into a JavaScript string literal; for more see
-     * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation) jsStringEnc(s, false, QuotationMode.NONE)}.
+     * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)
+     * jsStringEnc(s, JsStringEncCompatibility.JAVA_SCRIPT, null)}.
      */
     public static String javaScriptStringEnc(String s) {
-        return jsStringEnc(s, false);
+        return jsStringEnc(s, JsStringEncCompatibility.JAVA_SCRIPT);
     }
 
     /**
      * Escapes a {@link String} to be safely insertable into a JSON string literal; for more see
-     * {@link #jsStringEnc(String, boolean) jsStringEnc(s, true)}.
+     * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)
+     * jsStringEnc(s, JsStringEncCompatibility.JSON, null)}.
      */
     public static String jsonStringEnc(String s) {
-        return jsStringEnc(s, true);
+        return jsStringEnc(s, JsStringEncCompatibility.JSON);
     }
 
     private static final int NO_ESC = 0;
@@ -1342,15 +1344,28 @@
     
     /**
      * Escapes a {@link String} to be safely insertable into a JSON or JavaScript string literal; for more see
-     * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation) jsStringEnc(s, json, QuotationMode.NONE)}.
+     * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation)
+     * jsStringEnc(s, json ? JsStringEncCompatibility.JSON : JsStringEncCompatibility.JAVA_SCRIPT, null)}.
      *
      * @since 2.3.20
+     * @deprecated Use {@link #jsStringEnc(String, JsStringEncCompatibility)} instead.
      */
+    @Deprecated
     public static String jsStringEnc(String s, boolean json) {
         return jsStringEnc(s, json ? JsStringEncCompatibility.JSON : JsStringEncCompatibility.JAVA_SCRIPT, null);
     }
 
     /**
+     * Escapes a {@link String} to be safely insertable into a JSON or JavaScript string literal; for more see
+     * {@link #jsStringEnc(String, JsStringEncCompatibility, JsStringEncQuotation) jsStringEnc(s, compatibility, null)}.
+     *
+     * @since 2.3.32
+     */
+    public static String jsStringEnc(String s, JsStringEncCompatibility compatibility) {
+        return jsStringEnc(s, compatibility, null);
+    }
+
+    /**
      * Escapes a {@link String} to be safely insertable into a JavaScript or a JSON string literal, and if the 3rd
      * argument is {@code true}, also adds quotation marks around it.
      * If instead the caller ensures that the quotation marks are there, then in JSON mode (2nd argument), the quotation
@@ -1520,7 +1535,7 @@
             // Needs no escaping
                 
             if (sb != null) sb.append(c);
-        } // for each characters
+        } // for each character
 
         if (quotation != null) {
             sb.append(quotation.getSymbol());
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 2f9d3b7..8569625 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -10604,7 +10604,7 @@
         linkend="gloss.unicode">UNICODE</link> text" (UTF-16). Nonetheless,
         there are situations when it must deal with <link
         linkend="gloss.charset">charsets</link>, because it has to exchange
-        data with the outer world that may uses various other charsets.</para>
+        data with the outer world that may use various other charsets.</para>
 
         <section>
           <title>The charset of the input</title>
@@ -16269,7 +16269,7 @@
           <title>is_infinite</title>
 
           <indexterm>
-            <primary>is_infinte built-in</primary>
+            <primary>is_infinite built-in</primary>
           </indexterm>
 
           <indexterm>
@@ -28786,7 +28786,7 @@
           <answer>
             <para>FreeMarker uses the locale-sensitive number formatting
             capability of the Java platform. The default number format for
-            your locale may uses grouping or other formatting. If you don't
+            your locale may use grouping or other formatting. If you don't
             want that, you have to override the number format suggested by the
             Java platform with the <literal>number_format</literal> <link
             linkend="pgui_config_settings">FreeMarker setting</link>. For
diff --git a/src/manual/zh_CN/book.xml b/src/manual/zh_CN/book.xml
index 81ef7fb..e03ff44 100644
--- a/src/manual/zh_CN/book.xml
+++ b/src/manual/zh_CN/book.xml
@@ -20,10 +20,7 @@
 <book conformance="docgen" version="5.0" xml:lang="en"
       xmlns="http://docbook.org/ns/docbook"
       xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:ns5="http://www.w3.org/1999/xhtml"
-      xmlns:ns4="http://www.w3.org/2000/svg"
-      xmlns:ns3="http://www.w3.org/1998/Math/MathML"
-      xmlns:ns="http://docbook.org/ns/docbook">
+>
   <info>
     <title>FreeMarker 手册</title>
 
@@ -11427,7 +11424,7 @@
           <title>is_infinite</title>
 
           <indexterm>
-            <primary>is_infinte built-in</primary>
+            <primary>is_infinite built-in</primary>
           </indexterm>
 
           <indexterm>
diff --git a/src/test/java/freemarker/core/BooleanFormatEnvironmentCachingTest.java b/src/test/java/freemarker/core/BooleanFormatEnvironmentCachingTest.java
index d8ca4d8..0feb90c 100644
--- a/src/test/java/freemarker/core/BooleanFormatEnvironmentCachingTest.java
+++ b/src/test/java/freemarker/core/BooleanFormatEnvironmentCachingTest.java
@@ -45,13 +45,12 @@
                         + "<#setting cFormat='JSON'>${true} ${true} ${false} ${false} "
                         + "<#setting booleanFormat='y,n'>${true} ${true} ${false} ${false} "
                         + "<#setting cFormat='Java'>${true} ${true} ${false} ${false} "
-                        + "<#setting booleanFormat='c'>${true} ${true} ${false} ${false} ",
+                        + "<#setting booleanFormat='c'>${true} ${true} ${false} ${false}",
                 ""
                         + "TRUE TRUE FALSE FALSE "
                         + "true true false false "
                         + "y y n n "
                         + "y y n n "
-                        + "true true false false "
-                        + "");
+                        + "true true false false");
     }
 }
\ No newline at end of file
diff --git a/src/test/java/freemarker/core/CFormatTemplateTest.java b/src/test/java/freemarker/core/CFormatTemplateTest.java
index 414a901..fd8a66f 100644
--- a/src/test/java/freemarker/core/CFormatTemplateTest.java
+++ b/src/test/java/freemarker/core/CFormatTemplateTest.java
@@ -70,4 +70,10 @@
                         + "Java: \"a'b\\\"c\\u0001\" ");
     }
 
+    @Test
+    public void testUnsafeSetting() throws TemplateException, IOException {
+        assertErrorContains("<#setting c_format='com.example.ExploitCFormat()'>", "not allowed");
+        assertErrorContains("<#setting cFormat='com.example.ExploitCFormat()'>", "not allowed");
+    }
+
 }
diff --git a/src/test/java/freemarker/core/NumberBiTest.java b/src/test/java/freemarker/core/NumberBiTest.java
index 3f3bd44..e4c1a8c 100644
--- a/src/test/java/freemarker/core/NumberBiTest.java
+++ b/src/test/java/freemarker/core/NumberBiTest.java
@@ -51,10 +51,6 @@
         assertNumberBi("+1", "1");
     }
 
-    private void assertThrowsNumberFormatException(String s) {
-        assertErrorContains("${'" + s + "'?number}", NonNumericalException.class, "\"" + s + "\"");
-    }
-
     private final void assertNumberBi(String input, String output) throws TemplateException, IOException {
         assertOutput("${'" + input + "'?number?c}", output);
     }
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 756b3fa..72287e6 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -314,7 +314,7 @@
         assertTrue(cfg.isCFormatExplicitlySet());
         //
         cfg.unsetCFormat();
-        assertFalse(cfg.isTemplateLookupStrategyExplicitlySet());
+        assertFalse(cfg.isCFormatExplicitlySet());
     }
     
     public void testTemplateLoadingErrors() throws Exception {
@@ -1877,8 +1877,10 @@
             assertEquals(3, alg.getDefaultTerminatorLength());
             assertNull(alg.getDefaultMTerminator());
             assertNull(alg.getDefaultMTerminatorLength());
-            assertEquals(DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
-                    alg.getWordBoundaryMinLength());
+            assertEquals(
+                    DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
+                    alg.getWordBoundaryMinLength(),
+                    0);
         }
 
         {
@@ -1895,8 +1897,10 @@
             assertEquals("markupOutput(format=HTML, markup=<span class=trunc>...</span>)",
                     alg.getDefaultMTerminator().toString());
             assertEquals(Integer.valueOf(3), alg.getDefaultMTerminatorLength());
-            assertEquals(DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
-                    alg.getWordBoundaryMinLength());
+            assertEquals(
+                    DefaultTruncateBuiltinAlgorithm.DEFAULT_WORD_BOUNDARY_MIN_LENGTH,
+                    alg.getWordBoundaryMinLength(),
+                    0);
         }
 
         {
@@ -1931,8 +1935,8 @@
 
         for (CFormat standardCFormat : new CFormat[] {
                         LegacyCFormat.INSTANCE,
-                        JSONCFormat.INSTANCE, JavaScriptCFormat.INSTANCE, JavaCFormat.INSTANCE,
-                        XSCFormat.INSTANCE
+                        JSONCFormat.INSTANCE, JavaScriptCFormat.INSTANCE, JavaScriptOrJSONCFormat.INSTANCE,
+                        JavaCFormat.INSTANCE, XSCFormat.INSTANCE
                 }) {
             cfg.setSetting(Configuration.C_FORMAT_KEY, standardCFormat.getName());
             assertSame(standardCFormat, cfg.getCFormat());