PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.5
4.9.5 4.9.4 4.9.3 4.9.2 4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 All 203 releases
betterdocs / react-src / admin / analytics / components / common / geo.js

geo.js in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.9.5, at react-src/admin/analytics/components/common/geo.js

130 lines 5.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* Country + language helpers used by the Reader Engagement view.
2 Country codes are ISO-3166 alpha-2; flag emoji is built from the regional
3 indicator code points so we never have to ship a flag-name dictionary. */
4
5 const REGIONAL_INDICATOR_A = 0x1F1E6;
6
7 export function countryFlag( code ) {
8 if ( ! code || code.length !== 2 ) return "🏳";
9 const a = code.toUpperCase().charCodeAt( 0 ) - 65;
10 const b = code.toUpperCase().charCodeAt( 1 ) - 65;
11 if ( a < 0 || a > 25 || b < 0 || b > 25 ) return "🏳";
12 return String.fromCodePoint( REGIONAL_INDICATOR_A + a, REGIONAL_INDICATOR_A + b );
13 }
14
15 /* Centroid approximations (lat/lng) for ~60 common analytics countries — enough
16 for the world-map bubble overlay. Codes outside this set fall back to a
17 nullable centroid (skipped on the map, still listed). */
18 export const COUNTRY_DB = {
19 US: { name: "United States", lat: 38, lng: -97 },
20 IN: { name: "India", lat: 22, lng: 78 },
21 GB: { name: "United Kingdom", lat: 54, lng: -2 },
22 DE: { name: "Germany", lat: 51, lng: 10 },
23 BR: { name: "Brazil", lat: -14, lng: -51 },
24 FR: { name: "France", lat: 46, lng: 2 },
25 CA: { name: "Canada", lat: 56, lng: -106 },
26 AU: { name: "Australia", lat: -25, lng: 133 },
27 JP: { name: "Japan", lat: 36, lng: 138 },
28 ES: { name: "Spain", lat: 40, lng: -3 },
29 NL: { name: "Netherlands", lat: 52, lng: 5 },
30 SG: { name: "Singapore", lat: 1.4, lng: 103.8 },
31 IT: { name: "Italy", lat: 42, lng: 12 },
32 SE: { name: "Sweden", lat: 60, lng: 18 },
33 NO: { name: "Norway", lat: 61, lng: 8 },
34 DK: { name: "Denmark", lat: 56, lng: 10 },
35 FI: { name: "Finland", lat: 64, lng: 26 },
36 PL: { name: "Poland", lat: 52, lng: 20 },
37 IE: { name: "Ireland", lat: 53, lng: -8 },
38 BE: { name: "Belgium", lat: 50.5, lng: 4.5 },
39 CH: { name: "Switzerland", lat: 47, lng: 8 },
40 AT: { name: "Austria", lat: 47.5, lng: 14 },
41 PT: { name: "Portugal", lat: 39.5, lng: -8 },
42 GR: { name: "Greece", lat: 39, lng: 22 },
43 TR: { name: "Turkey", lat: 39, lng: 35 },
44 RU: { name: "Russia", lat: 62, lng: 95 },
45 UA: { name: "Ukraine", lat: 49, lng: 32 },
46 CZ: { name: "Czechia", lat: 50, lng: 15 },
47 RO: { name: "Romania", lat: 46, lng: 25 },
48 HU: { name: "Hungary", lat: 47, lng: 19 },
49 BG: { name: "Bulgaria", lat: 43, lng: 25 },
50 ZA: { name: "South Africa", lat: -30, lng: 25 },
51 NG: { name: "Nigeria", lat: 10, lng: 8 },
52 EG: { name: "Egypt", lat: 26, lng: 30 },
53 KE: { name: "Kenya", lat: -1, lng: 38 },
54 MA: { name: "Morocco", lat: 32, lng: -7 },
55 CN: { name: "China", lat: 35, lng: 105 },
56 KR: { name: "South Korea", lat: 36, lng: 128 },
57 TW: { name: "Taiwan", lat: 24, lng: 121 },
58 HK: { name: "Hong Kong", lat: 22.3, lng: 114.2 },
59 ID: { name: "Indonesia", lat: -2, lng: 118 },
60 TH: { name: "Thailand", lat: 15, lng: 101 },
61 VN: { name: "Vietnam", lat: 16, lng: 108 },
62 MY: { name: "Malaysia", lat: 4, lng: 102 },
63 PH: { name: "Philippines", lat: 13, lng: 122 },
64 PK: { name: "Pakistan", lat: 30, lng: 70 },
65 BD: { name: "Bangladesh", lat: 24, lng: 90 },
66 LK: { name: "Sri Lanka", lat: 7, lng: 81 },
67 IR: { name: "Iran", lat: 32, lng: 53 },
68 IL: { name: "Israel", lat: 31, lng: 35 },
69 AE: { name: "United Arab Emirates", lat: 24, lng: 54 },
70 SA: { name: "Saudi Arabia", lat: 24, lng: 45 },
71 MX: { name: "Mexico", lat: 23, lng: -102 },
72 AR: { name: "Argentina", lat: -38, lng: -64 },
73 CL: { name: "Chile", lat: -36, lng: -71 },
74 CO: { name: "Colombia", lat: 4, lng: -72 },
75 PE: { name: "Peru", lat: -10, lng: -75 },
76 VE: { name: "Venezuela", lat: 7, lng: -66 },
77 NZ: { name: "New Zealand", lat: -42, lng: 173 }
78 };
79
80 export function countryName( code ) {
81 if ( ! code ) return "";
82 const upper = code.toUpperCase();
83 if ( COUNTRY_DB[ upper ] ) return COUNTRY_DB[ upper ].name;
84 // Browser fallback for codes outside our short table.
85 try {
86 const n = new Intl.DisplayNames( [ "en" ], { type: "region" } ).of( upper );
87 return n && n !== upper ? n : upper;
88 } catch ( e ) { return upper; }
89 }
90
91 /* Language full name + code label — "English (en)", "Deutsch (de)", etc.
92 Uses the browser's Intl.DisplayNames; falls back to a short list, then the
93 raw code so we always render something readable. */
94 const LANG_FALLBACK = {
95 en: "English", es: "Spanish", fr: "French", de: "German", it: "Italian",
96 pt: "Portuguese", nl: "Dutch", sv: "Swedish", no: "Norwegian", da: "Danish",
97 fi: "Finnish", pl: "Polish", ru: "Russian", uk: "Ukrainian", cs: "Czech",
98 el: "Greek", tr: "Turkish", ar: "Arabic", he: "Hebrew", hi: "Hindi",
99 bn: "Bangla", ja: "Japanese", ko: "Korean", zh: "Chinese", id: "Indonesian",
100 th: "Thai", vi: "Vietnamese"
101 };
102
103 export function languageName( code ) {
104 if ( ! code ) return "—";
105 const lower = String( code ).toLowerCase().replace( /[_].*$/, "" ).slice( 0, 5 );
106 try {
107 const n = new Intl.DisplayNames( [ "en" ], { type: "language" } ).of( lower );
108 if ( n && n.toLowerCase() !== lower ) return `${ n } (${ lower })`;
109 } catch ( e ) {}
110 const root = lower.split( "-" )[ 0 ];
111 if ( LANG_FALLBACK[ root ] ) return `${ LANG_FALLBACK[ root ] } (${ lower })`;
112 return lower;
113 }
114
115 /* Client-side CSV download — assemble + trigger via a transient blob URL. */
116 export function downloadCsv( filename, rows ) {
117 if ( ! rows || ! rows.length ) return;
118 const esc = ( v ) => {
119 const s = String( v == null ? "" : v );
120 return /[",\n]/.test( s ) ? `"${ s.replace( /"/g, '""' ) }"` : s;
121 };
122 const csv = rows.map( ( r ) => r.map( esc ).join( "," ) ).join( "\n" );
123 const blob = new Blob( [ csv ], { type: "text/csv;charset=utf-8" } );
124 const url = URL.createObjectURL( blob );
125 const a = document.createElement( "a" );
126 a.href = url; a.download = filename;
127 document.body.appendChild( a ); a.click(); a.remove();
128 setTimeout( () => URL.revokeObjectURL( url ), 1000 );
129 }
130