Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add suggestions for spelling mistakes #101

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"react-scripts": "5.0.0",
"spherical-geometry-js": "^3.0.0",
"typescript": "^4.5.4",
"web-vitals": "^2.1.3"
"web-vitals": "^2.1.3",
"edit-distance": "^1.0.5"
},
"scripts": {
"start": "react-scripts start",
Expand Down
59 changes: 58 additions & 1 deletion src/components/Guesser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { LocaleContext } from "../i18n/LocaleContext";
import localeList from "../i18n/messages";
import { FormattedMessage } from "react-intl";
import { langNameMap } from "../i18n/locales";
import { levenshtein } from "edit-distance";

const countryData: Country[] = require("../data/country_data.json").features;

type Props = {
Expand Down Expand Up @@ -50,6 +52,55 @@ export default function Guesser({
});
}

function getCloseMatches(countryName: string, list: Country[], threshold: number): string[] {

const targets: string[] = alternateNames.map(pair => pair.alternative);

if (locale === "en-CA") {
list.forEach((country) => {
const { NAME, NAME_LONG, /*ABBREV,*/ ADMIN, BRK_NAME, NAME_SORT } =
country.properties;
targets.push(...[NAME, NAME_LONG,/* ABBREV,*/ ADMIN, BRK_NAME, NAME_SORT]);
});
} else {
list.forEach((country) => {
targets.push(country.properties[langName]);
});
}

const insertCost = (char: string) => 1;
const removeCost = (char: string) => 1;
const updateCost = (charA: string, charB: string) => {
//Accent error count as 0.5. Different letter count as 1.
return charA !== charB ? (removeAccent(charA) !== removeAccent(charB) ? 1 : 0.5) : 0;
};

const removeAccent = (char: string) => {
return char.normalize('NFD').replace(/[\u0300-\u036f]/g, "");
};

const computeDistance = (hypothesis: string, reference: string) => {
var result = levenshtein(hypothesis.toLowerCase(), reference.toLowerCase(), insertCost, removeCost, updateCost);
return result.distance;
}

const matches: { target: string, distance: number }[] = [];
targets.forEach(target => {
const distance = computeDistance(countryName, target);
if (distance <= threshold) {
matches.push({ distance, target })
}
});

matches.sort((a, b) => a.distance - b.distance);
var closeMatches = matches.map(element => element.target);

//remove duplicates
return closeMatches.reduce((a: string[], b: string) => {
if (a.indexOf(b) < 0) a.push(b);
return a;
}, []);
}
// Check territories function
function runChecks() {
const trimmedName = guessName
Expand All @@ -68,7 +119,13 @@ export default function Guesser({
}
const guessCountry = findCountry(userGuess, countryData);
if (!guessCountry) {
setError(localeList[locale]["Game5"]);
const closeMatches = getCloseMatches(userGuess, countryData, 2);
if (closeMatches.length > 0) {
setError(localeList[locale]["Game9"].replaceAll(/\{country\}/g, closeMatches.join(", ")));
}
else {
setError(localeList[locale]["Game5"]);
}
return;
}
if (practiceMode) {
Expand Down
1 change: 1 addition & 0 deletions src/i18n/messages/de-DE.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const German: Messages = {
Game6: "Das Land wurde schon versucht",
Game7: "Das geheime Land ist {answer}!",
Game8: "Nächste Grenze",
Game9: `Ungültiger Versuch. {country}?`, //TODO: translate
StatsTitle: "Statistiken",
Stats1: "Letzter Sieg",
Stats2: "Heutige Versuche",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/messages/en-CA.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const English: Messages = {
Game6: "Country already guessed",
Game7: "The Mystery Country is {answer}!",
Game8: "Closest border",
Game9: `Invalid guess. Did you mean {country}?`,
StatsTitle: "Statistics",
Stats1: "Last win",
Stats2: "Today's guesses",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/messages/es-MX.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const Spanish: Messages = {
Game6: "Pais ya adivinado",
Game7: "¡El País Secreto es {answer}!",
Game8: "Frontera más cercana",
Game9: `Intento inválido. {country}?`, //TODO: translate
StatsTitle: "Estadísticas",
Stats1: "Última victoria ",
Stats2: "Intentos de hoy",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/messages/fr-FR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const French: Messages = {
Game6: "Pays déjà tenté",
Game7: "Le pays mystère est {answer}!",
Game8: "Frontière la plus proche",
Game9: `Tentative invalide. Voulez-vous dire {country}?`,
StatsTitle: "Statistiques",
Stats1: "Dernière victoire",
Stats2: "Tentatives d'aujourd'hui",
Expand Down
1 change: 1 addition & 0 deletions src/i18n/messages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const Portuguese: Messages = {
Game6: "Você já chutou este país",
Game7: "O país misterioso é {answer}!",
Game8: "Fronteira mais próxima",
Game9: `Tentativa inválida. {country}?`, //TODO: translate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Game9: `Tentativa inválida. {country}?`, //TODO: translate
Game9: `Tentativa inválida. Queria dizer {country}?`,

StatsTitle: "Estatísticas",
Stats1: "Última vitória",
Stats2: "Palpites de hoje",
Expand Down
19 changes: 19 additions & 0 deletions src/lib/edit-distance.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
declare module "edit-distance" {
//Just adding the minimum type declarations for what we use of edit-distance.
export interface distanceInfo {
a: string;
b: string;
distance: number;
}

export type simpleCostFunction = (char: string) => number;
export type compareCostFunction = (charA: string, charB: string) => number;

export function levenshtein(
stringA: string,
stringB: string,
insertCb: simpleCostFunction,
removeCb: simpleCostFunction,
updateCb: compareCostFunction
): distanceInfo;
}
1 change: 1 addition & 0 deletions src/lib/locale.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export type Messages = {
Game6: string;
Game7: string;
Game8: string;
Game9: string;
StatsTitle: string;
Stats1: string;
Stats2: string;
Expand Down