Summary: - Grist document has a associated "locale" setting that affects how currency is formatted. - Currency selector for number format. Test Plan: not done Reviewers: dsagal Reviewed By: dsagal Subscribers: paulfitz Differential Revision: https://phab.getgrist.com/D2977pull/12/head
parent
e492dfdb22
commit
a6e08883e0
@ -0,0 +1,49 @@
|
||||
import {ACSelectItem, buildACSelect} from "app/client/lib/ACSelect";
|
||||
import {Computed, IDisposableOwner, Observable} from "grainjs";
|
||||
import {ACIndexImpl} from "app/client/lib/ACIndex";
|
||||
import {testId} from 'app/client/ui2018/cssVars';
|
||||
import {currencies} from 'app/common/Locales';
|
||||
|
||||
interface CurrencyPickerOptions {
|
||||
// The label to use in the select menu for the default option.
|
||||
defaultCurrencyLabel: string;
|
||||
}
|
||||
|
||||
export function buildCurrencyPicker(
|
||||
owner: IDisposableOwner,
|
||||
currency: Observable<string|undefined>,
|
||||
onSave: (value: string|undefined) => void,
|
||||
{defaultCurrencyLabel}: CurrencyPickerOptions
|
||||
) {
|
||||
const currencyItems: ACSelectItem[] = currencies
|
||||
.map(item => ({
|
||||
value: item.code,
|
||||
label: `${item.code} ${item.name}`,
|
||||
cleanText: `${item.code} ${item.name}`.trim().toLowerCase(),
|
||||
}));
|
||||
|
||||
// Add default currency label option to the very front.
|
||||
currencyItems.unshift({
|
||||
label : defaultCurrencyLabel,
|
||||
value : defaultCurrencyLabel,
|
||||
cleanText : defaultCurrencyLabel.toLowerCase(),
|
||||
});
|
||||
// Create a computed that will display 'Local currency' as a value and label
|
||||
// when `currency` is undefined.
|
||||
const valueObs = Computed.create(owner, (use) => use(currency) || defaultCurrencyLabel);
|
||||
const acIndex = new ACIndexImpl<ACSelectItem>(currencyItems, 200, true);
|
||||
return buildACSelect(owner,
|
||||
{
|
||||
acIndex, valueObs,
|
||||
save(_, item: ACSelectItem | undefined) {
|
||||
// Save only if we have found a match
|
||||
if (!item) {
|
||||
throw new Error("Invalid currency");
|
||||
}
|
||||
// For default value, return undefined to use default currency for document.
|
||||
onSave(item.value === defaultCurrencyLabel ? undefined : item.value);
|
||||
}
|
||||
},
|
||||
testId("currency-autocomplete")
|
||||
);
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export interface DocumentSettings {
|
||||
locale: string;
|
||||
currency?: string;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import LocaleCurrency = require('locale-currency/map');
|
||||
import {nativeCompare} from 'app/common/gutil';
|
||||
|
||||
const localeCodes = [
|
||||
"es-AR", "hy-AM", "en-AU", "az-AZ", "be-BY", "quz-BO", "pt-BR",
|
||||
"bg-BG", "en-CA", "arn-CL", "es-CO", "hr-HR", "cs-CZ", "da-DK",
|
||||
"es-EC", "ar-EG", "fi-FI", "fr-FR", "ka-GE", "de-DE", "el-GR", "en-HK",
|
||||
"hu-HU", "hi-IN", "id-ID", "ga-IE", "ar-IL", "it-IT", "ja-JP", "kk-KZ",
|
||||
"lv-LV", "lt-LT", "es-MX", "mn-MN", "my-MM", "nl-NL", "nb-NO",
|
||||
"es-PY", "ceb-PH", "pl-PL", "pt-PT", "ro-RO", "ru-RU", "sr-RS",
|
||||
"sk-SK", "sl-SI", "ko-KR", "es-ES", "sv-SE", "de-CH", "zh-TW", "th-TH",
|
||||
"tr-TR", "uk-UA", "en-GB", "en-US", "es-UY", "es-VE", "vi-VN"
|
||||
];
|
||||
|
||||
export interface Locale {
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export let locales: Readonly<Locale[]>;
|
||||
|
||||
// Intl.DisplayNames is only supported on recent browsers, so proceed with caution.
|
||||
try {
|
||||
const localeDisplay = new Intl.DisplayNames('en', {type: 'region'});
|
||||
locales = localeCodes.map(code => {
|
||||
return { name: localeDisplay.of(new Intl.Locale(code).region), code };
|
||||
});
|
||||
} catch {
|
||||
// Fall back to using the locale code as the display name.
|
||||
locales = localeCodes.map(code => ({ name: code, code }));
|
||||
}
|
||||
|
||||
export interface Currency {
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export let currencies: Readonly<Currency[]>;
|
||||
|
||||
// Intl.DisplayNames is only supported on recent browsers, so proceed with caution.
|
||||
try {
|
||||
const currencyDisplay = new Intl.DisplayNames('en', {type: 'currency'});
|
||||
currencies = [...new Set(Object.values(LocaleCurrency))].map(code => {
|
||||
return { name: currencyDisplay.of(code), code };
|
||||
});
|
||||
} catch {
|
||||
// Fall back to using the currency code as the display name.
|
||||
currencies = [...new Set(Object.values(LocaleCurrency))].map(code => {
|
||||
return { name: code, code };
|
||||
});
|
||||
}
|
||||
|
||||
currencies = [...currencies].sort((a, b) => nativeCompare(a.code, b.code));
|
@ -0,0 +1,16 @@
|
||||
import {parse as languageParser} from "accept-language-parser";
|
||||
import {Request} from 'express';
|
||||
import {locales} from 'app/common/Locales';
|
||||
|
||||
/**
|
||||
* Returns the locale from a request, falling back to `defaultLocale`
|
||||
* if unable to determine the locale.
|
||||
*/
|
||||
export function localeFromRequest(req: Request, defaultLocale: string = 'en-US') {
|
||||
const language = languageParser(req.headers["accept-language"] as string)[0];
|
||||
if (!language) { return defaultLocale; }
|
||||
|
||||
const locale = `${language.code}-${language.region}`;
|
||||
const supports = locales.some(l => l.code === locale);
|
||||
return supports ? locale : defaultLocale;
|
||||
}
|
Loading…
Reference in new issue