You could do something like this:
type T = number | string;
function numSorter(a: number, b: number){
return a - b;
}
function stringSorter(a: string, b: string, locale: string) {
const icf = new Intl.Collator(locale).compare;
return icf(a,b)
}
function picker(a: T, b: T, locale: string) {
if (typeof a === 'number' && typeof b === 'number') {
return numSorter(a,b);
} else if (typeof a === 'string' && typeof b === 'string') {
return stringSorter(a,b, locale);
} else {
throw Error("incompatible types");
}
}
function sorter(locale: string) {
return (a:T, b:T) => picker(a,b,locale);
}
const mySorter = sorter("US");
console.log(mySorter(1,2)); //both numbers, so ok
console.log(mySorter("world","hello")); // both strings, so ok
console.log(mySorter(1,"hello")); // incompatible types
Which gives an output like this:

I could be mistaken, but I think that this kind of approach defeats the purpose of static type checking. The generic reference still has to have a concrete implementation.
In this case, the operations you are performing are different for the different types number and string, so theres no overlap. The generic approach might be useful for objects that do have some overlap in functionality, like a common method they both have, like if you wanted a helper method for finding length of an array (as shown in the docs):
function loggingIdentity<Type>(arg: Array<Type>): Array<Type> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
This generic function will work for any array of any type, because any array has the length property.
Overall, I don't think its useful for this case, I think it might be better to have concrete implementations from the start, and calling the correct sorting function when needed. Generics are a bit of a headache to be honest, especially in TypeScript.