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:
home
2026-02-27 04:15:32 +03:00
parent 328d968f3f
commit 06fe57c765
285 changed files with 53132 additions and 1871 deletions

View File

@@ -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,