diff --git a/README.md b/README.md index ab739f2f..d70896aa 100644 --- a/README.md +++ b/README.md @@ -456,10 +456,15 @@ would be `["during", "rain", "next-wednesday"]`. * `RAIN_TYPE` * `SLEET_TYPE` * `SNOW_TYPE` -* `["parenthetical", SNOW_TYPE, SNOW_ACCUMULATION]`: For daily or weekly - summaries, if a significant amount of snow is expected, we will qualify it - with the amount of expected snow accumulation on the ground. (For example, - "snow (3-4 in.) throughout the day".) +* `["parenthetical", EXPECTED_PRECIP_TYPE, SNOW_ACCUMULATION]`: For daily or + weekly summaries, if a significant amount of snow is expected, we will + qualify it with the amount of expected snow accumulation. (For example, + "snow (3-4 in.) throughout the day".) PLEASE NOTE that it is possible for a + chance of snow accumulation to be forecasted even if the expected + precipitation type is rain or sleet: this may occur if the forecasted + temperature is right around the freezing point. Translations should clarify + that the parenthetical refers to a chance of snow in such circumstances. + (For example, "sleet (chance of 3-4 in. of snow) throughout the day".) In each of the below precipitation types, the intensity of precipitation is (very approximately) as follows: diff --git a/lib/lang/en.js b/lib/lang/en.js index 8914e4f7..9220f56a 100644 --- a/lib/lang/en.js +++ b/lib/lang/en.js @@ -6,26 +6,49 @@ function join_with_shared_prefix(a, b, joiner) { // HACK: This gets around "today through on Tuesday" or cases like it, which // are incorrect in English. - if(m === "today" || m === "tomorrow") + if(m === "today" || m === "tomorrow") { m = "on " + m; + } - while(i !== m.length && - i !== b.length && - m.charCodeAt(i) === b.charCodeAt(i)) + // Skip the prefix of b that is shared with a. + while( + i !== m.length && + i !== b.length && + m.charCodeAt(i) === b.charCodeAt(i) + ) { ++i; + } - while(i && m.charCodeAt(i - 1) !== 32) + // ...except whitespace! We need that whitespace! + while(i && b.charCodeAt(i - 1) !== 32) { --i; + } return a + joiner + b.slice(i); } function strip_prefix(period) { - return period.slice(0, 9) === "overnight" ? period.slice(4) : - period.slice(0, 7) === "in the " ? period.slice(7) : + return period.startsWith("overnight")? period.slice(4): + period.startsWith("in the ")? period.slice(7): period; } +function capitalize(str) { + // Do not capitalize articles, very short words, or units. + if( + str === "a" || + str === "and" || + str === "cm" || + str === "in" || + str === "of" || + str === "with" + ) { + return str; + } + + return str[0].toUpperCase() + str.slice(1); +} + module.exports = { "clear": "clear", "no-precipitation": "no precipitation", @@ -104,11 +127,7 @@ module.exports = { "centimeters": "$1 cm.", "less-than": "< $1", "and": function(a, b) { - return join_with_shared_prefix( - a, - b, - a.indexOf(",") !== -1 ? ", and " : " and " - ); + return join_with_shared_prefix(a, b, a.includes(",")? ", and ": " and "); }, "through": function(a, b) { return join_with_shared_prefix(a, b, " through "); @@ -116,7 +135,20 @@ module.exports = { "with": "$1, with $2", "range": "$1\u2013$2", "parenthetical": function(a, b) { - return a + " (" + b + (a === "mixed precipitation" ? " of snow)" : ")"); + // In the case of mixed precipitation, we want to clarify that the + // snow accumulation in the parenthetical is snow. In the case that it's + // of an unknown type or rain or sleet, we want to clarify that while snow + // isn't expected, it has a chance of occurring. The below checks do this. + + // HACK: These are not the best ways to determine the precipitation type... + if(!a.endsWith("flurries") && !a.endsWith("snow")) { + if(!a.startsWith("mixed")) { + b = "with a chance of " + b; + } + b += " of snow"; + } + + return a + " (" + b + ")"; }, "for-hour": "$1 for the hour", "starting-in": "$1 starting in $2", @@ -141,29 +173,25 @@ module.exports = { "temperatures-rising": "high temperatures rising to $1 $2", "temperatures-valleying": "high temperatures bottoming out at $1 $2", "temperatures-falling": "high temperatures falling to $1 $2", - // Capitalize the first letter of every word, except if that word is - // "and". (This is a very crude bastardization of proper English titling - // rules, but it is adequate for the purposes of this module.) + // Capitalize the first letter of every word except "and", "or", and units. + // (This is a very crude bastardization of proper English titling rules, but + // it is adequate for the purposes of this module.) "title": function(str) { - return str.replace( - /\b(?:a(?!nd\b)|c(?!m\.)|i(?!n\.)|[^\Waci])/g, - function(letter) { - return letter.toUpperCase(); - } - ); + return str.replace(/\w+/g, capitalize); }, - /* Capitalize the first word of the sentence and end with a period. */ + // Capitalize the first word of the sentence and end with a period. "sentence": function(str) { - /* Capitalize. */ - str = str.charAt(0).toUpperCase() + str.slice(1); + // Capitalize. + str = capitalize(str); - /* Add a period if there isn't already one. */ - if(str.charAt(str.length - 1) !== ".") + // Add a period if there isn't already one. + if(!str.endsWith(".")) { str += "."; + } return str; }, - "next-hour-forecast-status": "Next hour forecasts are $1 due to $2.", + "next-hour-forecast-status": "next hour forecasts are $1 due to $2", "unavailable": "unavailable", "temporarily-unavailable": "temporarily unavailable", "partially-unavailable": "partially unavailable", diff --git a/test_cases/en.json b/test_cases/en.json index 725aabe7..88474a0e 100644 --- a/test_cases/en.json +++ b/test_cases/en.json @@ -249,6 +249,9 @@ "Heavy Snow (3\u20135 cm.)": ["title", ["parenthetical", "heavy-snow", ["centimeters", ["range", 3, 5]]]], + "Rain (with a Chance of 2\u20134 in. of Snow)": + ["title", ["parenthetical", "medium-rain", ["inches", ["range", 2, 4]]]], + "Possible Thunderstorms": ["title", "possible-thunderstorm"], @@ -260,9 +263,11 @@ ["starting-in", "very-light-rain", ["less-than", ["minutes", 1]]]], "Next hour forecasts are temporarily unavailable due to all nearby radar stations being offline.": - ["sentence",["next-hour-forecast-status", "temporarily-unavailable", "station-offline"]], + ["sentence", ["next-hour-forecast-status", "temporarily-unavailable", "station-offline"]], + "Next hour forecasts are partially unavailable due to gaps in coverage from nearby radar stations.": - ["sentence",["next-hour-forecast-status", "partially-unavailable", "station-incomplete"]], + ["sentence", ["next-hour-forecast-status", "partially-unavailable", "station-incomplete"]], + "Next hour forecasts are unavailable due to all nearby radar stations being offline.": - ["sentence",["next-hour-forecast-status", "unavailable", "station-offline"]] + ["sentence", ["next-hour-forecast-status", "unavailable", "station-offline"]] }