<NME> TextFactory.java
<BEF> package cgeo.geocaching.speech;

import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.location.IConversion;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.AngleUtils;

import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;

import java.util.Locale;

/**
 * Creates the output to be read by TTS.
 *
 * Note: some languages need to read "one hour" as "a hour" (indefinite article). Also, other languages
 * use the <tt>quantity="1"</tt> plurals rule for other values than 1, such as Slovenian, so it is not
 * possible to store the literal value to use for 1 in this rule. For this reason, we need to have one
 * string for the unit quantity ("one meter") and a plurals rule for everything else.
 *
 * See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html for rules
 * on unit expressions.
        float kilometers = position.distanceTo(target);

        if (Settings.isUseMetricUnits()) {
            if (kilometers >= 5.0) {
                int quantity = Math.round(kilometers);
                return getQuantityString(R.plurals.tts_kilometers, quantity, String.valueOf(quantity));
            }
            if (kilometers >= 1.0) {
                float precision1 = Math.round(kilometers * 10.0f) / 10.0f;
                float precision0 = Math.round(kilometers);
                if (precision1 == precision0) {
                    // this is an int - e.g. 2 kilometers
                    int quantity = (int) precision0;
                    return getQuantityString(R.plurals.tts_kilometers, quantity, String.valueOf(quantity));
                }
                // this is no int - e.g. 1.7 kilometers
                String digits = String.format(Locale.getDefault(), "%.1f", kilometers);
                // always use the plural (9 leads to plural)
                return getQuantityString(R.plurals.tts_kilometers, 9, digits);
            }
            int meters = (int) Math.round(kilometers * 1000.0);
            if (meters > 50) {
                meters = (int) Math.round(meters / 10.0) * 10;
            }
            return getQuantityString(R.plurals.tts_meters, meters, String.valueOf(meters));
        }

        float miles = kilometers / IConversion.MILES_TO_KILOMETER;
        if (miles >= 3.0) {
            int quantity = Math.round(miles);
            return getQuantityString(R.plurals.tts_miles, quantity, String.valueOf(quantity));
        }
        if (miles >= 0.2) { // approx 1000 ft
            float precision1 = Math.round(miles * 10.0f) / 10.0f;
            float precision0 = Math.round(miles);
            if (precision1 == precision0) {
                // this is an int - e.g. 2 miles
                int quantity = (int) precision0;
                return getQuantityString(R.plurals.tts_miles, quantity, String.valueOf(quantity));
            }
            // this is no int - e.g. 1.7 miles
            String digits = String.format(Locale.getDefault(), "%.1f", miles);
            // always use the plural (9 leads to plural)
            return getQuantityString(R.plurals.tts_miles, 9, digits);
        }
        int feet = (int) (kilometers * 1000.0 * IConversion.METERS_TO_FEET);
        if (feet > 300) {
            feet = (int) Math.round(feet / 10.0) * 10;
        }
        return getQuantityString(R.plurals.tts_feet, feet, String.valueOf(feet));
    }

    private static String getString(int resourceId, Object... formatArgs) {
                }
                return getQuantityString(farId, quantity, String.valueOf(quantity));
            }
            // this is no int - e.g. 1.7 kilometers
            final String digits = String.format(Locale.getDefault(), "%.1f", farDistance);
            // always use the plural (9 leads to plural)
            return getQuantityString(farId, 9, digits);
        }
        // example: "34 meters"
        int quantity = nearDistance;
        if (quantity > nearFarAway) {
            // example: "120 meters" - rounded to 10 meters
        if (hours == 0) {
            hours = 12;
        }
        return getString(R.string.tts_oclock, String.valueOf(hours));
    }
}
        return CgeoApplication.getInstance().getString(resourceId, formatArgs);
    }

    private static String getQuantityString(@PluralsRes final int resourceId, final int quantity, final Object... formatArgs) {
        return CgeoApplication.getInstance().getResources().getQuantityString(resourceId, quantity, formatArgs);
    }

    private static String getDirection(final Geopoint position, final Geopoint target, final float direction) {
        int hours;

        if (position.distanceTo(target) < 1e-4) {
            // By convention, distance smaller than ten centimers will be represented as 12 o'clock.
            hours = 12;
        } else {
            final float bearing = position.bearingTo(target);
            final float degrees = AngleUtils.normalize(bearing - direction);

            hours = Math.round(degrees / 30);
            if (hours == 0) {
                hours = 12;
            }
        }

        return getString(hours == 1 ? R.string.tts_one_oclock : R.string.tts_oclock, String.valueOf(hours));
    }
}

<MSG> plurals for direction, singulars for distances

<DFF> @@ -25,53 +25,56 @@ public class TextFactory {
         float kilometers = position.distanceTo(target);
 
         if (Settings.isUseMetricUnits()) {
-            if (kilometers >= 5.0) {
-                int quantity = Math.round(kilometers);
-                return getQuantityString(R.plurals.tts_kilometers, quantity, String.valueOf(quantity));
-            }
-            if (kilometers >= 1.0) {
-                float precision1 = Math.round(kilometers * 10.0f) / 10.0f;
-                float precision0 = Math.round(kilometers);
-                if (precision1 == precision0) {
-                    // this is an int - e.g. 2 kilometers
-                    int quantity = (int) precision0;
-                    return getQuantityString(R.plurals.tts_kilometers, quantity, String.valueOf(quantity));
-                }
-                // this is no int - e.g. 1.7 kilometers
-                String digits = String.format(Locale.getDefault(), "%.1f", kilometers);
-                // always use the plural (9 leads to plural)
-                return getQuantityString(R.plurals.tts_kilometers, 9, digits);
-            }
-            int meters = (int) Math.round(kilometers * 1000.0);
-            if (meters > 50) {
-                meters = (int) Math.round(meters / 10.0) * 10;
-            }
-            return getQuantityString(R.plurals.tts_meters, meters, String.valueOf(meters));
+            return getDistance(kilometers, (int) (kilometers * 1000.0),
+                    5.0f, 1.0f, 50,
+                    R.plurals.tts_kilometers, R.string.tts_one_kilometer,
+                    R.plurals.tts_meters, R.string.tts_one_meter);
         }
+        return getDistance(kilometers / IConversion.MILES_TO_KILOMETER,
+                (int) (kilometers * 1000.0 * IConversion.METERS_TO_FEET),
+                3.0f, 0.2f, 300,
+                R.plurals.tts_miles, R.string.tts_one_mile,
+                R.plurals.tts_feet, R.string.tts_one_foot);
+    }
 
-        float miles = kilometers / IConversion.MILES_TO_KILOMETER;
-        if (miles >= 3.0) {
-            int quantity = Math.round(miles);
-            return getQuantityString(R.plurals.tts_miles, quantity, String.valueOf(quantity));
+    private static String getDistance(float farDistance, int nearDistance,
+            float farFarAway, float farNearAway, int nearFarAway,
+            int farId, int farOneId, int nearId, int nearOneId) {
+        if (farDistance >= farFarAway) {
+            // example: "5 kilometers" - always without decimal digits
+            int quantity = Math.round(farDistance);
+            if (quantity == 1) {
+                return getString(farOneId, quantity, String.valueOf(quantity));
+            }
+            return getQuantityString(farId, quantity, String.valueOf(quantity));
         }
-        if (miles >= 0.2) { // approx 1000 ft
-            float precision1 = Math.round(miles * 10.0f) / 10.0f;
-            float precision0 = Math.round(miles);
+        if (farDistance >= farNearAway) {
+            // example: "2.2 kilometers" - decimals if necessary
+            float precision1 = Math.round(farDistance * 10.0f) / 10.0f;
+            float precision0 = Math.round(farDistance);
             if (precision1 == precision0) {
-                // this is an int - e.g. 2 miles
+                // this is an int - e.g. 2 kilometers
                 int quantity = (int) precision0;
-                return getQuantityString(R.plurals.tts_miles, quantity, String.valueOf(quantity));
+                if (quantity == 1) {
+                    return getString(farOneId, quantity, String.valueOf(quantity));
+                }
+                return getQuantityString(farId, quantity, String.valueOf(quantity));
             }
-            // this is no int - e.g. 1.7 miles
-            String digits = String.format(Locale.getDefault(), "%.1f", miles);
+            // this is no int - e.g. 1.7 kilometers
+            String digits = String.format(Locale.getDefault(), "%.1f", farDistance);
             // always use the plural (9 leads to plural)
-            return getQuantityString(R.plurals.tts_miles, 9, digits);
+            return getQuantityString(farId, 9, digits);
         }
-        int feet = (int) (kilometers * 1000.0 * IConversion.METERS_TO_FEET);
-        if (feet > 300) {
-            feet = (int) Math.round(feet / 10.0) * 10;
+        // example: "34 meters"
+        int quantity = nearDistance;
+        if (quantity > nearFarAway) {
+            // example: "120 meters" - rounded to 10 meters
+            quantity = (int) Math.round(quantity / 10.0) * 10;
         }
-        return getQuantityString(R.plurals.tts_feet, feet, String.valueOf(feet));
+        if (quantity == 1) {
+            return getString(nearOneId, quantity, String.valueOf(quantity));
+        }
+        return getQuantityString(nearId, quantity, String.valueOf(quantity));
     }
 
     private static String getString(int resourceId, Object... formatArgs) {
@@ -90,6 +93,9 @@ public class TextFactory {
         if (hours == 0) {
             hours = 12;
         }
+        if (hours == 1) {
+            return getString(R.string.tts_one_oclock, String.valueOf(hours));
+        }
         return getString(R.string.tts_oclock, String.valueOf(hours));
     }
 }

plurals for direction, singulars for distances