feat: Go backend, enhanced search, new widgets, Docker deploy
Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
/**
|
||||
* Клиент для Localization Service.
|
||||
* Получает locale на основе геопозиции (через geo-device-service) и переводы.
|
||||
* Принудительно русский язык везде.
|
||||
*/
|
||||
|
||||
import { fetchContextWithGeolocation } from './geoDevice';
|
||||
import { localeFromCountryCode } from './localization/countryToLocale';
|
||||
|
||||
const LOCALE_API = '/api/locale';
|
||||
const TRANSLATIONS_API = '/api/translations';
|
||||
|
||||
@@ -52,83 +49,11 @@ export async function fetchLocaleWithClient(): Promise<LocalizationContext> {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/** IP-провайдеры возвращают country_code — fallback когда geo-device недоступен */
|
||||
async function fetchCountryFromIp(): Promise<string | null> {
|
||||
const providers: Array<{
|
||||
url: string;
|
||||
getCountry: (d: Record<string, unknown>) => string | null;
|
||||
}> = [
|
||||
{
|
||||
url: 'https://get.geojs.io/v1/ip/geo.json',
|
||||
getCountry: (d) => {
|
||||
const cc = d.country_code ?? d.country;
|
||||
return typeof cc === 'string' ? cc.toUpperCase() : null;
|
||||
},
|
||||
},
|
||||
{
|
||||
url: 'https://ipwhois.app/json/',
|
||||
getCountry: (d) => {
|
||||
const cc = d.country_code ?? d.country;
|
||||
return typeof cc === 'string' ? cc.toUpperCase() : null;
|
||||
},
|
||||
},
|
||||
];
|
||||
for (const p of providers) {
|
||||
try {
|
||||
const res = await fetch(p.url);
|
||||
const d = (await res.json()) as Record<string, unknown>;
|
||||
const country = p.getCountry(d);
|
||||
if (country) return country;
|
||||
} catch {
|
||||
/* следующий провайдер */
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const DEFAULT_LOCALE = 'ru';
|
||||
|
||||
/**
|
||||
* Получить locale с приоритетом geo-context.
|
||||
* По умолчанию — русский. Только если геопозиция из другой страны — используем её язык.
|
||||
*/
|
||||
/** Принудительно русский язык везде */
|
||||
export async function fetchLocaleWithGeoFirst(): Promise<LocalizationContext> {
|
||||
try {
|
||||
const ctx = await fetchContextWithGeolocation();
|
||||
const cc = ctx.geo?.countryCode;
|
||||
if (cc) {
|
||||
const locale = localeFromCountryCode(cc);
|
||||
return {
|
||||
locale,
|
||||
language: locale,
|
||||
region: ctx.geo?.region ?? null,
|
||||
countryCode: cc,
|
||||
timezone: ctx.geo?.timezone ?? null,
|
||||
source: 'geo',
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
/* geo-context недоступен */
|
||||
}
|
||||
try {
|
||||
const countryCode = await fetchCountryFromIp();
|
||||
if (countryCode) {
|
||||
const locale = localeFromCountryCode(countryCode);
|
||||
return {
|
||||
locale,
|
||||
language: locale,
|
||||
region: null,
|
||||
countryCode,
|
||||
timezone: null,
|
||||
source: 'geo',
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
/* IP fallback недоступен */
|
||||
}
|
||||
return {
|
||||
locale: DEFAULT_LOCALE,
|
||||
language: DEFAULT_LOCALE,
|
||||
locale: 'ru',
|
||||
language: 'ru',
|
||||
region: null,
|
||||
countryCode: null,
|
||||
timezone: null,
|
||||
|
||||
Reference in New Issue
Block a user