feat: add full i18n support for 14 languages with gettext PO translations

- Add i18n module with Locale enum, PO parser, and Accept-Language negotiation
- Create gettext .po translation files for en, es, de, ja, ko, id, fr, ru, pt, it, zh-Hant, zh-Hans, nl, ar
- Route HTML pages under /{lang}/ prefix using IETF language tags
- Auto-detect browser language and redirect from / to /{lang}/
- Add language selector dropdown in footer
- Localize geo names (country, city) from MMDB where available
- Add hreflang alternate links and multilingual sitemap for SEO
- Support RTL layout for Arabic
- Update integration tests for new routing structure
This commit is contained in:
NetworkCats
2026-03-04 20:11:16 +08:00
parent 503cb15674
commit 7e5a05b35a
30 changed files with 4132 additions and 121 deletions
Generated
+7
View File
@@ -1232,6 +1232,7 @@ dependencies = [
"tower",
"tracing",
"tracing-subscriber",
"urlencoding",
]
[[package]]
@@ -1741,6 +1742,12 @@ dependencies = [
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf8_iter"
version = "1.0.4"
+1
View File
@@ -15,6 +15,7 @@ reqwest = { version = "0.13", default-features = false, features = ["rustls"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
dotenvy = "0.15"
urlencoding = "2"
tokio-util = "0.7"
static-files = "0.3.1"
+187
View File
@@ -0,0 +1,187 @@
# Arabic translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: ar\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "ما هو عنوان IP الخاص بي؟"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "اعثر على عنوان IP العام الخاص بك على الفور. ابحث عن الموقع الجغرافي ورقم النظام المستقل والمنظمة واكتشاف البروكسي/VPN/Tor لأي عنوان IP."
msgid "Search IP address"
msgstr "البحث عن عنوان IP"
msgid "Type to search IP data"
msgstr "اكتب للبحث في بيانات IP"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "المنظمة"
msgid "Country"
msgstr "الدولة"
msgid "City"
msgstr "المدينة"
msgid "Proxy"
msgstr "بروكسي"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "استضافة"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "نعم"
msgid "No"
msgstr "لا"
msgid "FAQ"
msgstr "الأسئلة الشائعة"
msgid "How to use this service from the command line"
msgstr "كيفية استخدام هذه الخدمة من سطر الأوامر"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "يمكنك استخدام أدوات سطر الأوامر المختلفة مثل curl وhttpie وwget لاستخدام هذه الخدمة. باستخدام الأكثر شيوعًا، curl، كمثال:"
msgid "Query current IP:"
msgstr "الاستعلام عن عنوان IP الحالي:"
msgid "Query information for your own IP:"
msgstr "الاستعلام عن معلومات عنوان IP الخاص بك:"
msgid "Or display all information in JSON format:"
msgstr "أو عرض جميع المعلومات بصيغة JSON:"
msgid "Query information for a specific IP:"
msgstr "الاستعلام عن معلومات عنوان IP محدد:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "كيف أفرض عرض عنوان IPv4 أو IPv6 الخاص بي؟"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "يمكنك استخدام <code>curl -4</code> أو <code>curl -6</code> لعرض عنوان IPv4 أو IPv6 الخاص بك فقط على التوالي."
msgid "How to get a JSON format response?"
msgstr "كيف أحصل على استجابة بصيغة JSON؟"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "يمكنك إرسال طلب إلى نقطة النهاية <code>/json</code>، أو إرسال طلب مع ترويسة <code>application/json</code>."
msgid "Can I automate calls to your service via programs?"
msgstr "هل يمكنني أتمتة الاستدعاءات إلى خدمتكم عبر البرامج؟"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "بالتأكيد، طالما يتم احترام حد المعدل. في الظروف العادية، حد المعدل هو 30 طلبًا في الدقيقة (RPM)؛ وتحت الحمل العالي، سنزيد القيود إلى 10-5 طلبات في الدقيقة."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "واجهة برمجة التطبيقات الخاصة بنا مناسبة فقط للاستدعاءات اليدوية أو المشاريع الصغيرة. إذا كان موقعك الإلكتروني يستخدم واجهة برمجة التطبيقات الخاصة بنا للاستعلام عن عناوين IP للزوار، يرجى التأكد من استخدام قائمة انتظار الرسائل لإرسال الطلبات لتجنب الحجب. إذا كان مشروعك يتعامل مع حركة مرور عالية أو حساس لزمن الاستجابة، يرجى استخدام قاعدة البيانات المحلية مفتوحة المصدر: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>، وهي نفس قاعدة البيانات المستخدمة في هذا المشروع."
msgid "Where does the IP data come from?"
msgstr "من أين تأتي بيانات IP؟"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "تأتي البيانات الجغرافية لعناوين IP بشكل أساسي من قواعد البيانات المجانية لـ MaxMind وDB-IP؛ وتأتي بيانات AS من قاعدة بيانات IPinfo المجانية؛ وتأتي بيانات بروكسي IP من قاعدة بياناتي الخاصة <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>."
msgid "Data Attributions:"
msgstr "إسناد البيانات:"
msgid "Can I run my own service?"
msgstr "هل يمكنني تشغيل خدمتي الخاصة؟"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "نعم، الشيفرة المصدرية وقاعدة البيانات لهذا المشروع كلاهما مفتوح المصدر ومستضاف على GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "كيف يختلف هذا الموقع عن مواقع البحث عن IP الأخرى؟"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "الفرق الأكبر هو أن قاعدة بياناتنا تتضمن معلومات متعلقة بالبروكسي، والاستخبارات التي نقدمها عن البروكسي دقيقة للغاية. لقد قارنّاها بشكل خاص مع قواعد بيانات كشف البروكسي التجارية مثل IPinfo؛ دقة قاعدة بياناتنا مماثلة تمامًا للقواعد التجارية، بل إنها أكثر دقة في اكتشاف البروكسيات السكنية."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "ثانيًا، تغطيتنا الجغرافية لعناوين IPv6 أكثر شمولًا لأننا ندمج قواعد بيانات متعددة لتوفير بيانات أكثر اكتمالًا."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "تجربة المستخدم لدينا أيضًا أفضل من أي موقع بحث عن IP في السوق. يعمل الموقع بدون أي JavaScript، ونحن نحمّل فقط كمية ضئيلة من الموارد الخارجية (الخطوط والأيقونات)، مما يجعل سرعة التحميل الأولية سريعة للغاية."
msgid "Is this service stable?"
msgstr "هل هذه الخدمة مستقرة؟"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "منطق العمل لهذه الخدمة مكتوب بالكامل بلغة Rust، مما يوفر أداءً ممتازًا يضمن بقاء الموقع يعمل حتى تحت هجمات DDoS من الطبقة السابعة. يعمل الموقع على خادم VPS بمعالج ثنائي النواة و2 غيغابايت من الذاكرة العشوائية، وهو أكثر من كافٍ لهذا الإعداد -- ما لم نواجه هجوم DDoS ضخم من الطبقة السابعة، رغم أن لدينا Cloudflare أمامنا."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "تشغيل هذا الموقع يكلفني فقط 18.00 دولار شهريًا. لأن التكلفة منخفضة جدًا، لن أوقف العمليات بسبب تكاليف الأجهزة في المستقبل المنظور، ولن أضيف أي إعلانات."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "كن مطمئنًا، هذه الخدمة لن تقوم بـ \"Rug Pull\" بالتأكيد."
msgid "How can I sponsor this service?"
msgstr "كيف يمكنني رعاية هذه الخدمة؟"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "أقبل التبرعات بالعملات الرقمية. إذا كنت ترغب في الرعاية، يرجى التواصل:"
msgid "Website source code licensed under"
msgstr "الشيفرة المصدرية للموقع مرخصة بموجب"
msgid "Website Source"
msgstr "مصدر الموقع"
msgid "IP Database"
msgstr "قاعدة بيانات IP"
msgid "IP data provided by:"
msgstr "بيانات IP مقدمة من:"
msgid "Error"
msgstr "خطأ"
msgid "Go to Home"
msgstr "الذهاب إلى الصفحة الرئيسية"
msgid "IP address information"
msgstr "معلومات عنوان IP"
msgid "Frequently Asked Questions"
msgstr "الأسئلة الشائعة"
msgid "Project links"
msgstr "روابط المشروع"
msgid "Language"
msgstr "اللغة"
msgid "The page you are looking for does not exist."
msgstr "الصفحة التي تبحث عنها غير موجودة."
msgid "IP not found in database"
msgstr "لم يتم العثور على عنوان IP في قاعدة البيانات"
msgid "Invalid IP address"
msgstr "عنوان IP غير صالح"
msgid "Missing client IP address"
msgstr "عنوان IP للعميل مفقود"
msgid "Database lookup failed"
msgstr "فشل البحث في قاعدة البيانات"
msgid "Template render failed"
msgstr "فشل عرض القالب"
+187
View File
@@ -0,0 +1,187 @@
# German translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Wie lautet meine IP-Adresse?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Finden Sie Ihre öffentliche IP-Adresse sofort heraus. Suchen Sie Geolokalisierung, ASN, Organisation und Proxy-/VPN-/Tor-Erkennung für jede IP-Adresse."
msgid "Search IP address"
msgstr "IP-Adresse suchen"
msgid "Type to search IP data"
msgstr "Eingeben, um IP-Daten zu suchen"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "Land"
msgid "City"
msgstr "Stadt"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hosting"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Ja"
msgid "No"
msgstr "Nein"
msgid "FAQ"
msgstr "FAQ"
msgid "How to use this service from the command line"
msgstr "So verwenden Sie diesen Dienst über die Kommandozeile"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "Sie können verschiedene Kommandozeilen-Tools wie curl, httpie und wget verwenden, um diesen Dienst zu nutzen. Am Beispiel des beliebtesten Tools, curl:"
msgid "Query current IP:"
msgstr "Aktuelle IP abfragen:"
msgid "Query information for your own IP:"
msgstr "Informationen für Ihre eigene IP abfragen:"
msgid "Or display all information in JSON format:"
msgstr "Oder alle Informationen im JSON-Format anzeigen:"
msgid "Query information for a specific IP:"
msgstr "Informationen für eine bestimmte IP abfragen:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Wie erzwinge ich die Anzeige meiner IPv4- oder IPv6-Adresse?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "Sie können <code>curl -4</code> oder <code>curl -6</code> verwenden, um nur Ihre IPv4- bzw. IPv6-Adresse anzuzeigen."
msgid "How to get a JSON format response?"
msgstr "Wie erhalte ich eine Antwort im JSON-Format?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "Sie können eine Anfrage an den <code>/json</code>-Endpunkt senden oder eine Anfrage mit einem <code>application/json</code>-Header senden."
msgid "Can I automate calls to your service via programs?"
msgstr "Kann ich Aufrufe an Ihren Dienst per Programm automatisieren?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Selbstverstaendlich, solange das Anfragelimit eingehalten wird. Unter normalen Bedingungen betraegt das Limit 30 RPM (Anfragen pro Minute); bei hoher Last erhöhen wir die Beschraenkung auf 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "Unsere API ist nur für manuelle Aufrufe oder kleine Projekte geeignet. Wenn Ihre Website unsere API zur Abfrage von Besucher-IPs verwendet, stellen Sie bitte sicher, dass Sie eine Nachrichtenwarteschlange zum Senden von Anfragen verwenden, um Blockierungen zu vermeiden. Wenn Ihr Projekt hohen Datenverkehr hat oder latenzempfindlich ist, verwenden Sie bitte unsere Open-Source-Offline-Datenbank: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, die dieselbe Datenbank ist, die von diesem Projekt verwendet wird."
msgid "Where does the IP data come from?"
msgstr "Woher stammen die IP-Daten?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "IP-Geodaten stammen hauptsaechlich aus den kostenlosen Datenbanken von MaxMind und DB-IP; AS-Daten stammen aus der kostenlosen Datenbank von IPinfo; und IP-Proxy-Daten stammen aus meiner eigenen <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>-Datenbank."
msgid "Data Attributions:"
msgstr "Datenherkunft:"
msgid "Can I run my own service?"
msgstr "Kann ich meinen eigenen Dienst betreiben?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Ja, der Quellcode und die Datenbank für dieses Projekt sind beide Open Source und auf GitHub gehostet:"
msgid "How is this website different from other IP lookup sites?"
msgstr "Wie unterscheidet sich diese Website von anderen IP-Abfrage-Seiten?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "Der größte Unterschied besteht darin, dass unsere Datenbank Proxy-bezogene Informationen enthaelt und die von uns bereitgestellte Proxy-Erkennung sehr genau ist. Wir haben sie intern mit kommerziellen Proxy-Erkennungsdatenbanken wie IPinfo verglichen; die Praezision unserer Datenbank ist vollstaendig mit kommerziellen vergleichbar und bei der Erkennung von Residential-Proxys sogar noch genauer."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "Zweitens ist unsere geografische Abdeckung für IPv6 umfassender, da wir mehrere Datenbanken zusammenfuehren, um vollstaendigere Daten bereitzustellen."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Unsere Benutzererfahrung ist auch besser als bei jeder anderen IP-Abfrage-Seite auf dem Markt. Die Website laeuft ohne jegliches JavaScript, und wir laden nur eine minimale Menge externer Ressourcen (Schriftarten, Icons), wodurch die anfaengliche Ladegeschwindigkeit extrem schnell ist."
msgid "Is this service stable?"
msgstr "Ist dieser Dienst stabil?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "Die Geschäftslogik dieses Dienstes ist vollstaendig in Rust geschrieben und bietet eine hervorragende Leistung, die sicherstellt, dass die Website auch unter L7-DDoS-Angriffen betriebsbereit bleibt. Die Website laeuft auf einem VPS mit 2 Kernen und 2 GB RAM, was für dieses Setup bereits ueberdimensioniert ist -- es sei denn, wir werden von einem massiven L7-DDoS-Angriff getroffen, wobei wir Cloudflare vorgeschaltet haben."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Der Betrieb dieser Website kostet mich nur 18,00 $ pro Monat. Da die Kosten so gering sind, werde ich den Betrieb in absehbarer Zukunft weder wegen Hardwarekosten einstellen noch Werbung hinzufuegen."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Seien Sie versichert, dieser Dienst wird definitiv keinen \"Rug Pull\" durchfuehren."
msgid "How can I sponsor this service?"
msgstr "Wie kann ich diesen Dienst sponsern?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "Ich akzeptiere Kryptowaehrungsspenden. Wenn Sie sponsern möchten, kontaktieren Sie bitte:"
msgid "Website source code licensed under"
msgstr "Quellcode der Website lizenziert unter"
msgid "Website Source"
msgstr "Website-Quellcode"
msgid "IP Database"
msgstr "IP-Datenbank"
msgid "IP data provided by:"
msgstr "IP-Daten bereitgestellt von:"
msgid "Error"
msgstr "Fehler"
msgid "Go to Home"
msgstr "Zur Startseite"
msgid "IP address information"
msgstr "IP-Adressinformationen"
msgid "Frequently Asked Questions"
msgstr "Häufig gestellte Fragen"
msgid "Project links"
msgstr "Projektlinks"
msgid "Language"
msgstr "Sprache"
msgid "The page you are looking for does not exist."
msgstr "Die gesuchte Seite existiert nicht."
msgid "IP not found in database"
msgstr "IP nicht in der Datenbank gefunden"
msgid "Invalid IP address"
msgstr "Ungueltige IP-Adresse"
msgid "Missing client IP address"
msgstr "Fehlende Client-IP-Adresse"
msgid "Database lookup failed"
msgstr "Datenbankabfrage fehlgeschlagen"
msgid "Template render failed"
msgstr "Vorlagen-Rendering fehlgeschlagen"
+187
View File
@@ -0,0 +1,187 @@
# English translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "What is my IP address?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgid "Search IP address"
msgstr "Search IP address"
msgid "Type to search IP data"
msgstr "Type to search IP data"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "Country"
msgid "City"
msgstr "City"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hosting"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Yes"
msgid "No"
msgstr "No"
msgid "FAQ"
msgstr "FAQ"
msgid "How to use this service from the command line"
msgstr "How to use this service from the command line"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgid "Query current IP:"
msgstr "Query current IP:"
msgid "Query information for your own IP:"
msgstr "Query information for your own IP:"
msgid "Or display all information in JSON format:"
msgstr "Or display all information in JSON format:"
msgid "Query information for a specific IP:"
msgstr "Query information for a specific IP:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "How to force the display of my IPv4 or IPv6 address?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgid "How to get a JSON format response?"
msgstr "How to get a JSON format response?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgid "Can I automate calls to your service via programs?"
msgstr "Can I automate calls to your service via programs?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgid "Where does the IP data come from?"
msgstr "Where does the IP data come from?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgid "Data Attributions:"
msgstr "Data Attributions:"
msgid "Can I run my own service?"
msgstr "Can I run my own service?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "How is this website different from other IP lookup sites?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgid "Is this service stable?"
msgstr "Is this service stable?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Rest assured, this service will definitely not \"Rug Pull.\""
msgid "How can I sponsor this service?"
msgstr "How can I sponsor this service?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgid "Website source code licensed under"
msgstr "Website source code licensed under"
msgid "Website Source"
msgstr "Website Source"
msgid "IP Database"
msgstr "IP Database"
msgid "IP data provided by:"
msgstr "IP data provided by:"
msgid "Error"
msgstr "Error"
msgid "Go to Home"
msgstr "Go to Home"
msgid "IP address information"
msgstr "IP address information"
msgid "Frequently Asked Questions"
msgstr "Frequently Asked Questions"
msgid "Project links"
msgstr "Project links"
msgid "Language"
msgstr "Language"
msgid "The page you are looking for does not exist."
msgstr "The page you are looking for does not exist."
msgid "IP not found in database"
msgstr "IP not found in database"
msgid "Invalid IP address"
msgstr "Invalid IP address"
msgid "Missing client IP address"
msgstr "Missing client IP address"
msgid "Database lookup failed"
msgstr "Database lookup failed"
msgid "Template render failed"
msgstr "Template render failed"
+187
View File
@@ -0,0 +1,187 @@
# Spanish translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Cual es mi direccion IP?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Encuentra tu direccion IP publica al instante. Consulta la geolocalizacion, ASN, organizacion y deteccion de proxy/VPN/Tor para cualquier direccion IP."
msgid "Search IP address"
msgstr "Buscar direccion IP"
msgid "Type to search IP data"
msgstr "Escribe para buscar datos de IP"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "Pais"
msgid "City"
msgstr "Ciudad"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hosting"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Si"
msgid "No"
msgstr "No"
msgid "FAQ"
msgstr "Preguntas frecuentes"
msgid "How to use this service from the command line"
msgstr "Como usar este servicio desde la linea de comandos"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "Puedes usar diversas herramientas de linea de comandos como curl, httpie y wget para utilizar este servicio. Tomando la mas popular, curl, como ejemplo:"
msgid "Query current IP:"
msgstr "Consultar la IP actual:"
msgid "Query information for your own IP:"
msgstr "Consultar informacion de tu propia IP:"
msgid "Or display all information in JSON format:"
msgstr "O mostrar toda la informacion en formato JSON:"
msgid "Query information for a specific IP:"
msgstr "Consultar informacion de una IP especifica:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Como forzar la visualizacion de mi direccion IPv4 o IPv6?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "Puedes usar <code>curl -4</code> o <code>curl -6</code> para ver solo tu direccion IPv4 o IPv6 respectivamente."
msgid "How to get a JSON format response?"
msgstr "Como obtener una respuesta en formato JSON?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "Puedes enviar una solicitud al endpoint <code>/json</code>, o enviar una solicitud con el encabezado <code>application/json</code>."
msgid "Can I automate calls to your service via programs?"
msgstr "Puedo automatizar las llamadas a su servicio mediante programas?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Por supuesto, siempre que se respete el limite de velocidad. En condiciones normales, el limite es de 30 RPM (solicitudes por minuto); bajo alta carga, aumentaremos la restriccion a 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "Nuestra API solo es adecuada para llamadas manuales o proyectos de pequena escala. Si tu sitio web utiliza nuestra API para consultar las IP de los visitantes, asegurate de usar una cola de mensajes para enviar solicitudes y evitar bloqueos. Si tu proyecto tiene alto trafico o es sensible a la latencia, utiliza nuestra base de datos offline de codigo abierto: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, que es la misma base de datos utilizada por este proyecto."
msgid "Where does the IP data come from?"
msgstr "De donde provienen los datos de IP?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "Los datos geograficos de IP provienen principalmente de las bases de datos gratuitas de MaxMind y DB-IP; los datos de AS provienen de la base de datos gratuita de IPinfo; y los datos de proxy de IP provienen de mi propia base de datos <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>."
msgid "Data Attributions:"
msgstr "Atribuciones de datos:"
msgid "Can I run my own service?"
msgstr "Puedo ejecutar mi propio servicio?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Si, el codigo fuente y la base de datos de este proyecto son de codigo abierto y estan alojados en GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "En que se diferencia este sitio web de otros sitios de consulta de IP?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "La mayor diferencia es que nuestra base de datos incluye informacion relacionada con proxies, y la inteligencia de proxies que proporcionamos es muy precisa. La hemos comparado de forma privada con bases de datos comerciales de deteccion de proxies como IPinfo; la precision de nuestra base de datos es totalmente comparable con las comerciales, e incluso es mas precisa en la deteccion de proxies residenciales."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "En segundo lugar, nuestra cobertura geografica para IPv6 es mas completa porque fusionamos multiples bases de datos para proporcionar datos mas completos."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Nuestra experiencia de usuario tambien es mejor que la de cualquier sitio de consulta de IP del mercado. El sitio funciona sin ningun JavaScript, y solo cargamos una cantidad minima de recursos externos (fuentes, iconos), lo que hace que la velocidad de carga inicial sea extremadamente rapida."
msgid "Is this service stable?"
msgstr "Es estable este servicio?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "La logica de negocio de este servicio esta escrita completamente en Rust, ofreciendo un rendimiento excelente que asegura que el sitio permanezca operativo incluso bajo ataques DDoS de capa 7. El sitio funciona en un VPS con 2 nucleos y 2 GB de RAM, lo cual ya es mas que suficiente para esta configuracion, a menos que enfrentemos un ataque DDoS de capa 7 masivo, aunque tenemos Cloudflare delante de nosotros."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Ejecutar este sitio web solo me cuesta $18.00 al mes. Debido a que el costo es tan bajo, no dejare de operar por costos de hardware en el futuro previsible, ni agregare ningun anuncio."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Tengan la seguridad de que este servicio definitivamente no hara un \"Rug Pull.\""
msgid "How can I sponsor this service?"
msgstr "Como puedo patrocinar este servicio?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "Acepto donaciones en criptomonedas. Si deseas patrocinar, por favor contacta:"
msgid "Website source code licensed under"
msgstr "Codigo fuente del sitio web licenciado bajo"
msgid "Website Source"
msgstr "Codigo fuente del sitio web"
msgid "IP Database"
msgstr "Base de datos de IP"
msgid "IP data provided by:"
msgstr "Datos de IP proporcionados por:"
msgid "Error"
msgstr "Error"
msgid "Go to Home"
msgstr "Ir al inicio"
msgid "IP address information"
msgstr "Informacion de la direccion IP"
msgid "Frequently Asked Questions"
msgstr "Preguntas frecuentes"
msgid "Project links"
msgstr "Enlaces del proyecto"
msgid "Language"
msgstr "Idioma"
msgid "The page you are looking for does not exist."
msgstr "La pagina que buscas no existe."
msgid "IP not found in database"
msgstr "IP no encontrada en la base de datos"
msgid "Invalid IP address"
msgstr "Direccion IP no valida"
msgid "Missing client IP address"
msgstr "Falta la direccion IP del cliente"
msgid "Database lookup failed"
msgstr "La consulta a la base de datos fallo"
msgid "Template render failed"
msgstr "La renderizacion de la plantilla fallo"
+187
View File
@@ -0,0 +1,187 @@
# French translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Quelle est mon adresse IP ?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Trouvez votre adresse IP publique instantanement. Recherchez la geolocalisation, l'ASN, l'organisation et la detection de proxy/VPN/Tor pour n'importe quelle adresse IP."
msgid "Search IP address"
msgstr "Rechercher une adresse IP"
msgid "Type to search IP data"
msgstr "Saisissez pour rechercher des donnees IP"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "Pays"
msgid "City"
msgstr "Ville"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hebergement"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Oui"
msgid "No"
msgstr "Non"
msgid "FAQ"
msgstr "FAQ"
msgid "How to use this service from the command line"
msgstr "Comment utiliser ce service depuis la ligne de commande"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "Vous pouvez utiliser divers outils en ligne de commande tels que curl, httpie et wget pour utiliser ce service. En prenant le plus populaire, curl, comme exemple :"
msgid "Query current IP:"
msgstr "Interroger l'IP actuelle :"
msgid "Query information for your own IP:"
msgstr "Interroger les informations de votre propre IP :"
msgid "Or display all information in JSON format:"
msgstr "Ou afficher toutes les informations au format JSON :"
msgid "Query information for a specific IP:"
msgstr "Interroger les informations d'une IP specifique :"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Comment forcer l'affichage de mon adresse IPv4 ou IPv6 ?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "Vous pouvez utiliser <code>curl -4</code> ou <code>curl -6</code> pour afficher uniquement votre adresse IPv4 ou IPv6 respectivement."
msgid "How to get a JSON format response?"
msgstr "Comment obtenir une reponse au format JSON ?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "Vous pouvez envoyer une requete au point de terminaison <code>/json</code>, ou envoyer une requete avec un en-tete <code>application/json</code>."
msgid "Can I automate calls to your service via programs?"
msgstr "Puis-je automatiser les appels a votre service via des programmes ?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Certainement, tant que la limite de debit est respectee. Dans des conditions normales, la limite de debit est de 30 RPM (requetes par minute) ; en cas de forte charge, nous augmenterons la restriction a 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "Notre API est uniquement adaptee aux appels manuels ou aux projets a petite echelle. Si votre site web utilise notre API pour interroger les IP des visiteurs, veuillez vous assurer d'utiliser une file de messages pour envoyer les requetes afin d'eviter le blocage. Si votre projet a un trafic eleve ou est sensible a la latence, veuillez utiliser notre base de donnees hors ligne open source : <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, qui est la meme base de donnees utilisee par ce projet."
msgid "Where does the IP data come from?"
msgstr "D'ou proviennent les donnees IP ?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "Les donnees geographiques IP proviennent principalement des bases de donnees gratuites de MaxMind et DB-IP ; les donnees AS proviennent de la base de donnees gratuite d'IPinfo ; et les donnees de proxy IP proviennent de ma propre base de donnees <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>."
msgid "Data Attributions:"
msgstr "Attributions des donnees :"
msgid "Can I run my own service?"
msgstr "Puis-je executer mon propre service ?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Oui, le code source et la base de donnees de ce projet sont tous deux open source et heberges sur GitHub :"
msgid "How is this website different from other IP lookup sites?"
msgstr "En quoi ce site web est-il different des autres sites de recherche IP ?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "La plus grande difference est que notre base de donnees inclut des informations relatives aux proxys, et les renseignements sur les proxys que nous fournissons sont tres precis. Nous l'avons compare en prive avec des bases de donnees commerciales de detection de proxys comme IPinfo ; la precision de notre base de donnees est tout a fait comparable a celles des bases commerciales, et elle est encore plus precise pour detecter les proxys residentiels."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "Deuxiemement, notre couverture geographique pour IPv6 est plus complete car nous fusionnons plusieurs bases de donnees pour fournir des donnees plus exhaustives."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Notre experience utilisateur est egalement meilleure que celle de tout autre site de recherche IP sur le marche. Le site fonctionne sans aucun JavaScript, et nous ne chargeons qu'un minimum de ressources externes (polices, icones), ce qui rend la vitesse de chargement initiale extremement rapide."
msgid "Is this service stable?"
msgstr "Ce service est-il stable ?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "La logique metier de ce service est entierement ecrite en Rust, offrant d'excellentes performances qui garantissent que le site reste operationnel meme en cas d'attaques DDoS L7. Le site fonctionne sur un VPS avec 2 coeurs et 2 Go de RAM, ce qui est deja surdimensionne pour cette configuration -- sauf si nous subissons une attaque DDoS L7 massive, bien que nous ayons Cloudflare devant nous."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Faire fonctionner ce site web ne me coute que 18,00 $ par mois. Le cout etant si bas, je n'arreterai pas les operations en raison des couts materiels dans un avenir previsible, et je n'ajouterai aucune publicite."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Soyez rassure, ce service ne fera definitivement pas de \"Rug Pull\"."
msgid "How can I sponsor this service?"
msgstr "Comment puis-je sponsoriser ce service ?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "J'accepte les dons en cryptomonnaie. Si vous souhaitez sponsoriser, veuillez contacter :"
msgid "Website source code licensed under"
msgstr "Code source du site web sous licence"
msgid "Website Source"
msgstr "Code source du site"
msgid "IP Database"
msgstr "Base de donnees IP"
msgid "IP data provided by:"
msgstr "Donnees IP fournies par :"
msgid "Error"
msgstr "Erreur"
msgid "Go to Home"
msgstr "Retour a l'accueil"
msgid "IP address information"
msgstr "Informations sur l'adresse IP"
msgid "Frequently Asked Questions"
msgstr "Questions frequemment posees"
msgid "Project links"
msgstr "Liens du projet"
msgid "Language"
msgstr "Langue"
msgid "The page you are looking for does not exist."
msgstr "La page que vous recherchez n'existe pas."
msgid "IP not found in database"
msgstr "IP introuvable dans la base de donnees"
msgid "Invalid IP address"
msgstr "Adresse IP invalide"
msgid "Missing client IP address"
msgstr "Adresse IP du client manquante"
msgid "Database lookup failed"
msgstr "Echec de la recherche dans la base de donnees"
msgid "Template render failed"
msgstr "Echec du rendu du modele"
+187
View File
@@ -0,0 +1,187 @@
# Indonesian translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Apa alamat IP saya?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Temukan alamat IP publik Anda secara instan. Cari geolokasi, ASN, organisasi, dan deteksi proxy/VPN/Tor untuk alamat IP apa pun."
msgid "Search IP address"
msgstr "Cari alamat IP"
msgid "Type to search IP data"
msgstr "Ketik untuk mencari data IP"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "Negara"
msgid "City"
msgstr "Kota"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hosting"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Ya"
msgid "No"
msgstr "Tidak"
msgid "FAQ"
msgstr "FAQ"
msgid "How to use this service from the command line"
msgstr "Cara menggunakan layanan ini dari baris perintah"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "Anda dapat menggunakan berbagai alat baris perintah seperti curl, httpie, dan wget untuk menggunakan layanan ini. Mengambil yang paling populer, curl, sebagai contoh:"
msgid "Query current IP:"
msgstr "Kueri IP saat ini:"
msgid "Query information for your own IP:"
msgstr "Kueri informasi untuk IP Anda sendiri:"
msgid "Or display all information in JSON format:"
msgstr "Atau tampilkan semua informasi dalam format JSON:"
msgid "Query information for a specific IP:"
msgstr "Kueri informasi untuk IP tertentu:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Bagaimana cara memaksa tampilan alamat IPv4 atau IPv6 saya?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "Anda dapat menggunakan <code>curl -4</code> atau <code>curl -6</code> untuk melihat hanya alamat IPv4 atau IPv6 Anda."
msgid "How to get a JSON format response?"
msgstr "Bagaimana cara mendapatkan respons dalam format JSON?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "Anda dapat mengirim permintaan ke endpoint <code>/json</code>, atau mengirim permintaan dengan header <code>application/json</code>."
msgid "Can I automate calls to your service via programs?"
msgstr "Bisakah saya mengotomatiskan panggilan ke layanan Anda melalui program?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Tentu saja, selama batas laju dipatuhi. Dalam kondisi normal, batas laju adalah 30 RPM (Permintaan Per Menit); dalam beban tinggi, kami akan meningkatkan pembatasan menjadi 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "API kami hanya cocok untuk panggilan manual atau proyek berskala kecil. Jika situs web Anda menggunakan API kami untuk mengkueri IP pengunjung, pastikan Anda menggunakan antrean pesan untuk mengirim permintaan agar tidak terblokir. Jika proyek Anda memiliki lalu lintas tinggi atau sensitif terhadap latensi, silakan gunakan basis data offline sumber terbuka kami: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, yang merupakan basis data yang sama yang digunakan oleh proyek ini."
msgid "Where does the IP data come from?"
msgstr "Dari mana data IP berasal?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "Data geografis IP terutama berasal dari basis data gratis MaxMind dan DB-IP; data AS berasal dari basis data gratis IPinfo; dan data proxy IP berasal dari basis data <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> milik saya sendiri."
msgid "Data Attributions:"
msgstr "Atribusi Data:"
msgid "Can I run my own service?"
msgstr "Bisakah saya menjalankan layanan saya sendiri?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Ya, kode sumber dan basis data untuk proyek ini keduanya bersumber terbuka dan dihosting di GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "Apa perbedaan situs web ini dari situs pencarian IP lainnya?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "Perbedaan terbesar adalah basis data kami mencakup informasi terkait proxy, dan inteligensi proxy yang kami sediakan sangat akurat. Kami telah membandingkannya secara privat dengan basis data deteksi proxy komersial seperti IPinfo; presisi basis data kami sepenuhnya sebanding dengan yang komersial, dan bahkan lebih akurat dalam mendeteksi proxy residensial."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "Kedua, cakupan geografis kami untuk IPv6 lebih komprehensif karena kami menggabungkan beberapa basis data untuk menyediakan data yang lebih lengkap."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "UX kami juga lebih baik daripada situs pencarian IP mana pun di pasaran. Situs ini berjalan tanpa JavaScript apa pun, dan kami hanya memuat sumber daya eksternal dalam jumlah minimal (font, ikon), membuat kecepatan pemuatan awal sangat cepat."
msgid "Is this service stable?"
msgstr "Apakah layanan ini stabil?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "Logika bisnis layanan ini ditulis sepenuhnya dalam Rust, menawarkan performa luar biasa yang memastikan situs tetap beroperasi bahkan di bawah serangan DDoS L7. Situs ini berjalan di VPS dengan 2 core dan 2 GB RAM, yang sudah lebih dari cukup untuk konfigurasi ini -- kecuali kami menghadapi serangan DDoS L7 besar-besaran, meskipun kami memiliki Cloudflare di depan kami."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Menjalankan situs web ini hanya menghabiskan biaya $18,00 per bulan. Karena biayanya sangat rendah, saya tidak akan menghentikan operasi karena biaya perangkat keras di masa mendatang, dan juga tidak akan menambahkan iklan apa pun."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Tenang saja, layanan ini pasti tidak akan melakukan \"Rug Pull.\""
msgid "How can I sponsor this service?"
msgstr "Bagaimana cara mensponsori layanan ini?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "Saya menerima donasi mata uang kripto. Jika Anda ingin mensponsori, silakan hubungi:"
msgid "Website source code licensed under"
msgstr "Kode sumber situs web dilisensikan di bawah"
msgid "Website Source"
msgstr "Kode Sumber Situs"
msgid "IP Database"
msgstr "Basis Data IP"
msgid "IP data provided by:"
msgstr "Data IP disediakan oleh:"
msgid "Error"
msgstr "Kesalahan"
msgid "Go to Home"
msgstr "Ke Beranda"
msgid "IP address information"
msgstr "Informasi alamat IP"
msgid "Frequently Asked Questions"
msgstr "Pertanyaan yang Sering Diajukan"
msgid "Project links"
msgstr "Tautan proyek"
msgid "Language"
msgstr "Bahasa"
msgid "The page you are looking for does not exist."
msgstr "Halaman yang Anda cari tidak ditemukan."
msgid "IP not found in database"
msgstr "IP tidak ditemukan dalam basis data"
msgid "Invalid IP address"
msgstr "Alamat IP tidak valid"
msgid "Missing client IP address"
msgstr "Alamat IP klien tidak ditemukan"
msgid "Database lookup failed"
msgstr "Pencarian basis data gagal"
msgid "Template render failed"
msgstr "Render template gagal"
+187
View File
@@ -0,0 +1,187 @@
# Italian translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Qual e il mio indirizzo IP?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Trova il tuo indirizzo IP pubblico istantaneamente. Cerca geolocalizzazione, ASN, organizzazione e rilevamento proxy/VPN/Tor per qualsiasi indirizzo IP."
msgid "Search IP address"
msgstr "Cerca indirizzo IP"
msgid "Type to search IP data"
msgstr "Digita per cercare dati IP"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "Paese"
msgid "City"
msgstr "Citta"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hosting"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Si"
msgid "No"
msgstr "No"
msgid "FAQ"
msgstr "FAQ"
msgid "How to use this service from the command line"
msgstr "Come utilizzare questo servizio dalla riga di comando"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "Puoi utilizzare vari strumenti da riga di comando come curl, httpie e wget per usare questo servizio. Prendendo come esempio il piu popolare, curl:"
msgid "Query current IP:"
msgstr "Interroga l'IP corrente:"
msgid "Query information for your own IP:"
msgstr "Interroga le informazioni per il tuo IP:"
msgid "Or display all information in JSON format:"
msgstr "Oppure visualizza tutte le informazioni in formato JSON:"
msgid "Query information for a specific IP:"
msgstr "Interroga le informazioni per un IP specifico:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Come forzare la visualizzazione del mio indirizzo IPv4 o IPv6?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "Puoi usare <code>curl -4</code> o <code>curl -6</code> per visualizzare rispettivamente solo il tuo indirizzo IPv4 o IPv6."
msgid "How to get a JSON format response?"
msgstr "Come ottenere una risposta in formato JSON?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "Puoi inviare una richiesta all'endpoint <code>/json</code>, oppure inviare una richiesta con un'intestazione <code>application/json</code>."
msgid "Can I automate calls to your service via programs?"
msgstr "Posso automatizzare le chiamate al vostro servizio tramite programmi?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Certamente, a condizione che il limite di frequenza venga rispettato. In condizioni normali, il limite e di 30 RPM (richieste al minuto); sotto carico elevato, aumenteremo la restrizione a 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "La nostra API e adatta solo per chiamate manuali o progetti su piccola scala. Se il tuo sito web utilizza la nostra API per interrogare gli IP dei visitatori, assicurati di utilizzare una coda di messaggi per inviare le richieste ed evitare il blocco. Se il tuo progetto ha un traffico elevato o e sensibile alla latenza, utilizza il nostro database offline open-source: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, che e lo stesso database utilizzato da questo progetto."
msgid "Where does the IP data come from?"
msgstr "Da dove provengono i dati IP?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "I dati geografici IP provengono principalmente dai database gratuiti di MaxMind e DB-IP; i dati AS provengono dal database gratuito di IPinfo; e i dati proxy IP provengono dal mio database <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>."
msgid "Data Attributions:"
msgstr "Attribuzioni dei dati:"
msgid "Can I run my own service?"
msgstr "Posso eseguire il mio proprio servizio?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Si, il codice sorgente e il database di questo progetto sono entrambi open-source e ospitati su GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "In cosa si differenzia questo sito web dagli altri siti di ricerca IP?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "La differenza principale e che il nostro database include informazioni relative ai proxy, e l'intelligence sui proxy che forniamo e altamente accurata. Abbiamo effettuato confronti privati con database commerciali di rilevamento proxy come IPinfo; la precisione del nostro database e pienamente comparabile a quella dei database commerciali, ed e ancora piu accurata nel rilevamento dei proxy residenziali."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "In secondo luogo, la nostra copertura geografica per IPv6 e piu completa perche uniamo piu database per fornire dati piu esaustivi."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Anche la nostra UX e migliore rispetto a qualsiasi sito di ricerca IP sul mercato. Il sito funziona senza alcun JavaScript e carichiamo solo una quantita minima di risorse esterne (font, icone), rendendo la velocita di caricamento iniziale estremamente rapida."
msgid "Is this service stable?"
msgstr "Questo servizio e stabile?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "La logica di business di questo servizio e scritta interamente in Rust, offrendo prestazioni eccellenti che garantiscono che il sito rimanga operativo anche sotto attacchi DDoS L7. Il sito funziona su un VPS con 2 core e 2 GB di RAM, che e gia sovradimensionato per questa configurazione -- a meno che non subiamo un massiccio attacco DDoS L7, anche se abbiamo Cloudflare davanti a noi."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Gestire questo sito web mi costa solo $18,00 al mese. Poiche il costo e cosi basso, non interrompero le operazioni a causa dei costi hardware nel prossimo futuro, ne aggiungero alcuna pubblicita."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "State tranquilli, questo servizio non fara assolutamente \"Rug Pull.\""
msgid "How can I sponsor this service?"
msgstr "Come posso sponsorizzare questo servizio?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "Accetto donazioni in criptovaluta. Se desideri sponsorizzare, contatta:"
msgid "Website source code licensed under"
msgstr "Codice sorgente del sito web concesso in licenza sotto"
msgid "Website Source"
msgstr "Codice sorgente del sito"
msgid "IP Database"
msgstr "Database IP"
msgid "IP data provided by:"
msgstr "Dati IP forniti da:"
msgid "Error"
msgstr "Errore"
msgid "Go to Home"
msgstr "Vai alla Home"
msgid "IP address information"
msgstr "Informazioni sull'indirizzo IP"
msgid "Frequently Asked Questions"
msgstr "Domande frequenti"
msgid "Project links"
msgstr "Link del progetto"
msgid "Language"
msgstr "Lingua"
msgid "The page you are looking for does not exist."
msgstr "La pagina che stai cercando non esiste."
msgid "IP not found in database"
msgstr "IP non trovato nel database"
msgid "Invalid IP address"
msgstr "Indirizzo IP non valido"
msgid "Missing client IP address"
msgstr "Indirizzo IP del client mancante"
msgid "Database lookup failed"
msgstr "Ricerca nel database fallita"
msgid "Template render failed"
msgstr "Rendering del template fallito"
+187
View File
@@ -0,0 +1,187 @@
# Japanese translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "私のIPアドレスは何ですか?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "パブリックIPアドレスを即座に確認できます。任意のIPアドレスの位置情報、ASN、組織、プロキシ/VPN/Tor検出を調べることができます。"
msgid "Search IP address"
msgstr "IPアドレスを検索"
msgid "Type to search IP data"
msgstr "IPデータを検索するには入力してください"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "組織"
msgid "Country"
msgstr "国"
msgid "City"
msgstr "都市"
msgid "Proxy"
msgstr "プロキシ"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "ホスティング"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "はい"
msgid "No"
msgstr "いいえ"
msgid "FAQ"
msgstr "よくある質問"
msgid "How to use this service from the command line"
msgstr "コマンドラインからこのサービスを利用する方法"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "curl、httpie、wgetなどのさまざまなコマンドラインツールを使ってこのサービスを利用できます。最も一般的なcurlを例にとると:"
msgid "Query current IP:"
msgstr "現在のIPを照会:"
msgid "Query information for your own IP:"
msgstr "自分のIPの情報を照会:"
msgid "Or display all information in JSON format:"
msgstr "またはすべての情報をJSON形式で表示:"
msgid "Query information for a specific IP:"
msgstr "特定のIPの情報を照会:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "IPv4またはIPv6アドレスの表示を強制するには?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "<code>curl -4</code> または <code>curl -6</code> を使用することで、それぞれIPv4またはIPv6アドレスのみを表示できます。"
msgid "How to get a JSON format response?"
msgstr "JSON形式のレスポンスを取得するには?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "<code>/json</code> エンドポイントにリクエストを送信するか、<code>application/json</code> ヘッダーを付けてリクエストを送信してください。"
msgid "Can I automate calls to your service via programs?"
msgstr "プログラムでこのサービスへの呼び出しを自動化できますか?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "もちろんです。レート制限を守っていただければ問題ありません。通常時のレート制限は30 RPM(リクエスト/分)ですが、高負荷時には10~5 RPMに制限を強化する場合があります。"
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "当APIは手動での呼び出しや小規模プロジェクトにのみ適しています。ウェブサイトで当APIを使用して訪問者のIPを照会する場合は、ブロッキングを避けるためにメッセージキューを使用してリクエストを送信してください。高トラフィックまたはレイテンシに敏感なプロジェクトの場合は、当プロジェクトと同じデータベースを使用しているオープンソースのオフラインデータベース:<a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a> をご利用ください。"
msgid "Where does the IP data come from?"
msgstr "IPデータはどこから取得していますか?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "IP地理データは主にMaxMindとDB-IPの無料データベースから取得しています。ASデータはIPinfoの無料データベースから取得し、IPプロキシデータは独自の <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> データベースから取得しています。"
msgid "Data Attributions:"
msgstr "データ帰属表示:"
msgid "Can I run my own service?"
msgstr "自分でサービスを運営できますか?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "はい、このプロジェクトのソースコードとデータベースはどちらもオープンソースでGitHubにホストされています:"
msgid "How is this website different from other IP lookup sites?"
msgstr "このウェブサイトは他のIP検索サイトとどう違いますか?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "最大の違いは、当データベースにプロキシ関連情報が含まれており、提供するプロキシインテリジェンスの精度が非常に高いことです。IPinfoなどの商用プロキシ検出データベースと内部で比較したところ、当データベースの精度は商用のものと十分に匹敵し、レジデンシャルプロキシの検出においてはさらに高い精度を誇ります。"
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "次に、複数のデータベースを統合してより完全なデータを提供しているため、IPv6の地理的カバレッジがより包括的です。"
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "UXも市場のどのIP検索サイトよりも優れています。サイトはJavaScriptなしで動作し、最小限の外部リソース(フォント、アイコン)のみを読み込むため、初回読み込み速度が非常に高速です。"
msgid "Is this service stable?"
msgstr "このサービスは安定していますか?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "このサービスのビジネスロジックはすべてRustで書かれており、優れたパフォーマンスによりL7 DDoS攻撃下でもサイトの稼働を維持できます。サイトは2コア・2GB RAMのVPSで運営されていますが、この構成には十分すぎるスペックです。大規模なL7 DDoS攻撃を受けない限り問題ありませんが、前段にCloudflareを配置しています。"
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "このウェブサイトの運営コストは月額わずか18.00ドルです。コストが非常に低いため、当面の間ハードウェアコストが原因でサービスを停止することはなく、広告を掲載することもありません。"
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "ご安心ください。このサービスは決して「Rug Pull(突然の撤退)」をしません。"
msgid "How can I sponsor this service?"
msgstr "このサービスを支援するには?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "暗号通貨での寄付を受け付けています。スポンサーを希望される方は、こちらにご連絡ください:"
msgid "Website source code licensed under"
msgstr "ウェブサイトのソースコードのライセンス"
msgid "Website Source"
msgstr "ウェブサイトソース"
msgid "IP Database"
msgstr "IPデータベース"
msgid "IP data provided by:"
msgstr "IPデータ提供元:"
msgid "Error"
msgstr "エラー"
msgid "Go to Home"
msgstr "ホームに戻る"
msgid "IP address information"
msgstr "IPアドレス情報"
msgid "Frequently Asked Questions"
msgstr "よくある質問"
msgid "Project links"
msgstr "プロジェクトリンク"
msgid "Language"
msgstr "言語"
msgid "The page you are looking for does not exist."
msgstr "お探しのページは存在しません。"
msgid "IP not found in database"
msgstr "IPがデータベースに見つかりません"
msgid "Invalid IP address"
msgstr "無効なIPアドレス"
msgid "Missing client IP address"
msgstr "クライアントIPアドレスがありません"
msgid "Database lookup failed"
msgstr "データベース検索に失敗しました"
msgid "Template render failed"
msgstr "テンプレートのレンダリングに失敗しました"
+187
View File
@@ -0,0 +1,187 @@
# Korean translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: ko\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "내 IP 주소는 무엇인가요?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "공인 IP 주소를 즉시 확인하세요. 모든 IP 주소의 지리적 위치, ASN, 조직 정보, 프록시/VPN/Tor 탐지 결과를 조회할 수 있습니다."
msgid "Search IP address"
msgstr "IP 주소 검색"
msgid "Type to search IP data"
msgstr "IP 데이터를 검색하려면 입력하세요"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "조직"
msgid "Country"
msgstr "국가"
msgid "City"
msgstr "도시"
msgid "Proxy"
msgstr "프록시"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "호스팅"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "예"
msgid "No"
msgstr "아니오"
msgid "FAQ"
msgstr "자주 묻는 질문"
msgid "How to use this service from the command line"
msgstr "명령줄에서 이 서비스를 사용하는 방법"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "curl, httpie, wget 등 다양한 명령줄 도구를 사용하여 이 서비스를 이용할 수 있습니다. 가장 널리 사용되는 curl을 예로 들면:"
msgid "Query current IP:"
msgstr "현재 IP 조회:"
msgid "Query information for your own IP:"
msgstr "자신의 IP 정보 조회:"
msgid "Or display all information in JSON format:"
msgstr "또는 모든 정보를 JSON 형식으로 표시:"
msgid "Query information for a specific IP:"
msgstr "특정 IP 정보 조회:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "IPv4 또는 IPv6 주소만 강제로 표시하려면 어떻게 하나요?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "<code>curl -4</code> 또는 <code>curl -6</code>을 사용하여 각각 IPv4 또는 IPv6 주소만 확인할 수 있습니다."
msgid "How to get a JSON format response?"
msgstr "JSON 형식의 응답을 받으려면 어떻게 하나요?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "<code>/json</code> 엔드포인트로 요청을 보내거나, <code>application/json</code> 헤더를 포함하여 요청을 보낼 수 있습니다."
msgid "Can I automate calls to your service via programs?"
msgstr "프로그램을 통해 이 서비스에 자동으로 요청할 수 있나요?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "물론입니다. 요청 제한을 준수하기만 하면 됩니다. 일반적인 상황에서 요청 제한은 30 RPM(분당 요청 수)이며, 높은 부하 상황에서는 10-5 RPM으로 제한이 강화됩니다."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "저희 API는 수동 호출이나 소규모 프로젝트에만 적합합니다. 웹사이트에서 저희 API를 사용하여 방문자 IP를 조회하는 경우, 차단을 방지하기 위해 메시지 큐를 사용하여 요청을 보내주세요. 트래픽이 많거나 지연 시간에 민감한 프로젝트의 경우, 이 프로젝트에서 사용하는 것과 동일한 오픈소스 오프라인 데이터베이스를 사용해 주세요: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>."
msgid "Where does the IP data come from?"
msgstr "IP 데이터는 어디에서 가져오나요?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "IP 지리 데이터는 주로 MaxMind와 DB-IP의 무료 데이터베이스에서 제공됩니다. AS 데이터는 IPinfo의 무료 데이터베이스에서, IP 프록시 데이터는 자체 <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> 데이터베이스에서 제공됩니다."
msgid "Data Attributions:"
msgstr "데이터 출처:"
msgid "Can I run my own service?"
msgstr "자체 서비스를 운영할 수 있나요?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "네, 이 프로젝트의 소스 코드와 데이터베이스는 모두 오픈소스이며 GitHub에서 호스팅됩니다:"
msgid "How is this website different from other IP lookup sites?"
msgstr "이 웹사이트는 다른 IP 조회 사이트와 어떻게 다른가요?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "가장 큰 차이점은 저희 데이터베이스에 프록시 관련 정보가 포함되어 있으며, 제공하는 프록시 인텔리전스의 정확도가 매우 높다는 것입니다. IPinfo 같은 상용 프록시 탐지 데이터베이스와 비공개로 비교한 결과, 저희 데이터베이스의 정밀도는 상용 제품과 동등하며 주거용 프록시 탐지에서는 더 정확합니다."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "또한, 여러 데이터베이스를 병합하여 더 완전한 데이터를 제공하기 때문에 IPv6에 대한 지리적 커버리지가 더 포괄적입니다."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "저희의 사용자 경험도 시중의 어떤 IP 조회 사이트보다 뛰어납니다. 사이트는 JavaScript 없이 작동하며, 최소한의 외부 리소스(글꼴, 아이콘)만 로드하여 초기 로딩 속도가 매우 빠릅니다."
msgid "Is this service stable?"
msgstr "이 서비스는 안정적인가요?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "이 서비스의 비즈니스 로직은 전부 Rust로 작성되어 뛰어난 성능을 제공하며, L7 DDoS 공격 하에서도 사이트가 정상 운영될 수 있습니다. 사이트는 2코어, 2GB RAM의 VPS에서 운영되며, 이 구성에는 이미 과한 사양입니다 -- 대규모 L7 DDoS 공격이 발생하지 않는 한 말이죠. 물론 앞단에 Cloudflare가 있습니다."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "이 웹사이트 운영 비용은 월 $18.00에 불과합니다. 비용이 매우 낮기 때문에 가까운 미래에 하드웨어 비용으로 인해 운영을 중단하거나 광고를 추가하는 일은 없을 것입니다."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "안심하세요, 이 서비스는 절대로 \"Rug Pull\"하지 않을 것입니다."
msgid "How can I sponsor this service?"
msgstr "이 서비스를 후원하려면 어떻게 하나요?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "암호화폐 기부를 받고 있습니다. 후원을 원하시면 다음으로 연락해 주세요:"
msgid "Website source code licensed under"
msgstr "웹사이트 소스 코드 라이선스"
msgid "Website Source"
msgstr "웹사이트 소스"
msgid "IP Database"
msgstr "IP 데이터베이스"
msgid "IP data provided by:"
msgstr "IP 데이터 제공:"
msgid "Error"
msgstr "오류"
msgid "Go to Home"
msgstr "홈으로 이동"
msgid "IP address information"
msgstr "IP 주소 정보"
msgid "Frequently Asked Questions"
msgstr "자주 묻는 질문"
msgid "Project links"
msgstr "프로젝트 링크"
msgid "Language"
msgstr "언어"
msgid "The page you are looking for does not exist."
msgstr "찾고 있는 페이지가 존재하지 않습니다."
msgid "IP not found in database"
msgstr "데이터베이스에서 IP를 찾을 수 없습니다"
msgid "Invalid IP address"
msgstr "유효하지 않은 IP 주소"
msgid "Missing client IP address"
msgstr "클라이언트 IP 주소가 없습니다"
msgid "Database lookup failed"
msgstr "데이터베이스 조회 실패"
msgid "Template render failed"
msgstr "템플릿 렌더링 실패"
+223
View File
@@ -0,0 +1,223 @@
# RustyIP Translation Template
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Project-Id-Version: rustyip\n"
"POT-Creation-Date: 2026-03-04\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: templates/index.html - page title
msgid "What is my IP address?"
msgstr ""
#: templates/index.html - meta description
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr ""
#: templates/_search_form.html - search label
msgid "Search IP address"
msgstr ""
#: templates/_search_form.html - placeholder
msgid "Type to search IP data"
msgstr ""
#: templates/index.html - table header
msgid "ASN"
msgstr ""
#: templates/index.html - table header
msgid "Org"
msgstr ""
#: templates/index.html - table header
msgid "Country"
msgstr ""
#: templates/index.html - table header
msgid "City"
msgstr ""
#: templates/index.html - table header
msgid "Proxy"
msgstr ""
#: templates/index.html - table header
msgid "VPN"
msgstr ""
#: templates/index.html - table header
msgid "Hosting"
msgstr ""
#: templates/index.html - table header
msgid "Tor"
msgstr ""
#: templates/index.html - boolean yes
msgid "Yes"
msgstr ""
#: templates/index.html - boolean no
msgid "No"
msgstr ""
#: templates/index.html - FAQ section title
msgid "FAQ"
msgstr ""
#: templates/index.html - FAQ question
msgid "How to use this service from the command line"
msgstr ""
#: templates/index.html - FAQ answer
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr ""
#: templates/index.html - FAQ label
msgid "Query current IP:"
msgstr ""
#: templates/index.html - FAQ label
msgid "Query information for your own IP:"
msgstr ""
#: templates/index.html - FAQ label
msgid "Or display all information in JSON format:"
msgstr ""
#: templates/index.html - FAQ label
msgid "Query information for a specific IP:"
msgstr ""
#: templates/index.html - FAQ question
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr ""
#: templates/index.html - FAQ answer
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr ""
#: templates/index.html - FAQ question
msgid "How to get a JSON format response?"
msgstr ""
#: templates/index.html - FAQ answer
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr ""
#: templates/index.html - FAQ question
msgid "Can I automate calls to your service via programs?"
msgstr ""
#: templates/index.html - FAQ answer paragraph 1
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr ""
#: templates/index.html - FAQ answer paragraph 2
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr ""
#: templates/index.html - FAQ question
msgid "Where does the IP data come from?"
msgstr ""
#: templates/index.html - FAQ answer paragraph 1
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr ""
#: templates/index.html - FAQ answer label
msgid "Data Attributions:"
msgstr ""
#: templates/index.html - FAQ question
msgid "Can I run my own service?"
msgstr ""
#: templates/index.html - FAQ answer
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr ""
#: templates/index.html - FAQ question
msgid "How is this website different from other IP lookup sites?"
msgstr ""
#: templates/index.html - FAQ answer paragraph 1
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr ""
#: templates/index.html - FAQ answer paragraph 2
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr ""
#: templates/index.html - FAQ answer paragraph 3
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr ""
#: templates/index.html - FAQ question
msgid "Is this service stable?"
msgstr ""
#: templates/index.html - FAQ answer paragraph 1
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr ""
#: templates/index.html - FAQ answer paragraph 2
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr ""
#: templates/index.html - FAQ answer paragraph 3
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr ""
#: templates/index.html - FAQ question
msgid "How can I sponsor this service?"
msgstr ""
#: templates/index.html - FAQ answer
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr ""
#: templates/_footer.html - license
msgid "Website source code licensed under"
msgstr ""
#: templates/_footer.html - link text
msgid "Website Source"
msgstr ""
#: templates/_footer.html - link text
msgid "IP Database"
msgstr ""
#: templates/index.html - footer attribution
msgid "IP data provided by:"
msgstr ""
#: templates/error_alert.html - title
msgid "Error"
msgstr ""
#: templates/error_page.html - link
msgid "Go to Home"
msgstr ""
#: templates/index.html - table aria label
msgid "IP address information"
msgstr ""
#: templates/index.html - FAQ aria label
msgid "Frequently Asked Questions"
msgstr ""
#: templates/_footer.html - nav aria label
msgid "Project links"
msgstr ""
#: templates/base.html - language selector label
msgid "Language"
msgstr ""
+187
View File
@@ -0,0 +1,187 @@
# Dutch translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Wat is mijn IP-adres?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Vind direct uw openbare IP-adres. Zoek geolocatie, ASN, organisatie en proxy/VPN/Tor-detectie op voor elk IP-adres."
msgid "Search IP address"
msgstr "IP-adres zoeken"
msgid "Type to search IP data"
msgstr "Typ om IP-gegevens te zoeken"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "Land"
msgid "City"
msgstr "Stad"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hosting"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Ja"
msgid "No"
msgstr "Nee"
msgid "FAQ"
msgstr "FAQ"
msgid "How to use this service from the command line"
msgstr "Hoe deze dienst te gebruiken vanaf de opdrachtregel"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "U kunt verschillende opdrachtregelprogramma's zoals curl, httpie en wget gebruiken om deze dienst te gebruiken. Met de populairste, curl, als voorbeeld:"
msgid "Query current IP:"
msgstr "Huidig IP opvragen:"
msgid "Query information for your own IP:"
msgstr "Informatie opvragen voor uw eigen IP:"
msgid "Or display all information in JSON format:"
msgstr "Of toon alle informatie in JSON-formaat:"
msgid "Query information for a specific IP:"
msgstr "Informatie opvragen voor een specifiek IP:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Hoe forceer ik de weergave van mijn IPv4- of IPv6-adres?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "U kunt <code>curl -4</code> of <code>curl -6</code> gebruiken om respectievelijk alleen uw IPv4- of IPv6-adres te bekijken."
msgid "How to get a JSON format response?"
msgstr "Hoe krijg ik een antwoord in JSON-formaat?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "U kunt een verzoek sturen naar het <code>/json</code>-eindpunt, of een verzoek sturen met een <code>application/json</code>-header."
msgid "Can I automate calls to your service via programs?"
msgstr "Kan ik aanroepen naar uw dienst automatiseren via programma's?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Zeker, zolang de snelheidslimiet wordt gerespecteerd. Onder normale omstandigheden is de snelheidslimiet 30 RPM (verzoeken per minuut); bij hoge belasting verhogen we de beperking naar 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "Onze API is alleen geschikt voor handmatige aanroepen of kleinschalige projecten. Als uw website onze API gebruikt om bezoeker-IP's op te vragen, zorg er dan voor dat u een berichtenwachtrij gebruikt om verzoeken te verzenden en blokkering te voorkomen. Als uw project veel verkeer heeft of latentiegevoelig is, gebruik dan onze open-source offlinedatabase: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, dezelfde database die door dit project wordt gebruikt."
msgid "Where does the IP data come from?"
msgstr "Waar komen de IP-gegevens vandaan?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "IP-geografische gegevens komen voornamelijk uit de gratis databases van MaxMind en DB-IP; AS-gegevens komen uit de gratis database van IPinfo; en IP-proxygegevens komen uit mijn eigen <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>-database."
msgid "Data Attributions:"
msgstr "Databronvermeldingen:"
msgid "Can I run my own service?"
msgstr "Kan ik mijn eigen dienst draaien?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Ja, de broncode en database voor dit project zijn beide open-source en gehost op GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "Hoe verschilt deze website van andere IP-opzoeksites?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "Het grootste verschil is dat onze database proxyinformatie bevat en dat de proxy-intelligentie die wij bieden zeer nauwkeurig is. We hebben deze intern vergeleken met commerciele proxydetectiedatabases zoals IPinfo; de precisie van onze database is volledig vergelijkbaar met commerciele databases, en is zelfs nauwkeuriger bij het detecteren van residentieel proxies."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "Ten tweede is onze geografische dekking voor IPv6 uitgebreider omdat we meerdere databases samenvoegen om completere gegevens te bieden."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Onze UX is ook beter dan elke IP-opzoeksite op de markt. De site draait zonder JavaScript en we laden slechts een minimale hoeveelheid externe bronnen (lettertypen, pictogrammen), waardoor de initiele laadsnelheid extreem snel is."
msgid "Is this service stable?"
msgstr "Is deze dienst stabiel?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "De bedrijfslogica van deze dienst is volledig geschreven in Rust en biedt uitstekende prestaties die ervoor zorgen dat de site operationeel blijft, zelfs bij L7 DDoS-aanvallen. De site draait op een VPS met 2 cores en 2 GB RAM, wat al ruim voldoende is voor deze opzet -- tenzij we een enorme L7 DDoS-aanval tegenkomen, hoewel we Cloudflare voor ons hebben."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Het draaien van deze website kost mij slechts $18,00 per maand. Omdat de kosten zo laag zijn, zal ik de dienst in de voorzienbare toekomst niet stopzetten vanwege hardwarekosten, noch zal ik advertenties toevoegen."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Wees gerust, deze dienst zal absoluut geen \"Rug Pull\" doen."
msgid "How can I sponsor this service?"
msgstr "Hoe kan ik deze dienst sponsoren?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "Ik accepteer cryptovalutadonaties. Als u wilt sponsoren, neem dan contact op met:"
msgid "Website source code licensed under"
msgstr "Broncode van de website gelicentieerd onder"
msgid "Website Source"
msgstr "Websitebroncode"
msgid "IP Database"
msgstr "IP-database"
msgid "IP data provided by:"
msgstr "IP-gegevens geleverd door:"
msgid "Error"
msgstr "Fout"
msgid "Go to Home"
msgstr "Ga naar startpagina"
msgid "IP address information"
msgstr "IP-adresinformatie"
msgid "Frequently Asked Questions"
msgstr "Veelgestelde vragen"
msgid "Project links"
msgstr "Projectlinks"
msgid "Language"
msgstr "Taal"
msgid "The page you are looking for does not exist."
msgstr "De pagina die u zoekt bestaat niet."
msgid "IP not found in database"
msgstr "IP niet gevonden in database"
msgid "Invalid IP address"
msgstr "Ongeldig IP-adres"
msgid "Missing client IP address"
msgstr "Ontbrekend client-IP-adres"
msgid "Database lookup failed"
msgstr "Database-opzoeking mislukt"
msgid "Template render failed"
msgstr "Sjabloonweergave mislukt"
+187
View File
@@ -0,0 +1,187 @@
# Portuguese translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: pt\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Qual é o meu endereço IP?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Encontre seu endereço IP público instantaneamente. Consulte geolocalização, ASN, organização e detecção de proxy/VPN/Tor para qualquer endereço IP."
msgid "Search IP address"
msgstr "Pesquisar endereço IP"
msgid "Type to search IP data"
msgstr "Digite para pesquisar dados de IP"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Org"
msgid "Country"
msgstr "País"
msgid "City"
msgstr "Cidade"
msgid "Proxy"
msgstr "Proxy"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Hospedagem"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Sim"
msgid "No"
msgstr "Não"
msgid "FAQ"
msgstr "FAQ"
msgid "How to use this service from the command line"
msgstr "Como usar este serviço pela linha de comando"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "Você pode usar várias ferramentas de linha de comando, como curl, httpie e wget, para utilizar este serviço. Tomando a mais popular, curl, como exemplo:"
msgid "Query current IP:"
msgstr "Consultar IP atual:"
msgid "Query information for your own IP:"
msgstr "Consultar informações do seu próprio IP:"
msgid "Or display all information in JSON format:"
msgstr "Ou exibir todas as informações em formato JSON:"
msgid "Query information for a specific IP:"
msgstr "Consultar informações de um IP específico:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Como forçar a exibição do meu endereço IPv4 ou IPv6?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "Você pode usar <code>curl -4</code> ou <code>curl -6</code> para visualizar apenas seu endereço IPv4 ou IPv6, respectivamente."
msgid "How to get a JSON format response?"
msgstr "Como obter uma resposta em formato JSON?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "Você pode enviar uma requisição para o endpoint <code>/json</code>, ou enviar uma requisição com o cabeçalho <code>application/json</code>."
msgid "Can I automate calls to your service via programs?"
msgstr "Posso automatizar chamadas ao seu serviço por meio de programas?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Certamente, desde que o limite de requisições seja respeitado. Em condições normais, o limite é de 30 RPM (Requisições Por Minuto); sob alta carga, aumentaremos a restrição para 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "Nossa API é adequada apenas para chamadas manuais ou projetos de pequena escala. Se o seu site usa nossa API para consultar IPs de visitantes, certifique-se de usar uma fila de mensagens para enviar requisições e evitar bloqueios. Se o seu projeto tem alto tráfego ou é sensível à latência, use nosso banco de dados offline de código aberto: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, que é o mesmo banco de dados usado por este projeto."
msgid "Where does the IP data come from?"
msgstr "De onde vêm os dados de IP?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "Os dados geográficos de IP vêm principalmente dos bancos de dados gratuitos do MaxMind e DB-IP; os dados de AS vêm do banco de dados gratuito do IPinfo; e os dados de proxy de IP vêm do meu próprio banco de dados <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>."
msgid "Data Attributions:"
msgstr "Atribuições de Dados:"
msgid "Can I run my own service?"
msgstr "Posso executar meu próprio serviço?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Sim, o código-fonte e o banco de dados deste projeto são ambos de código aberto e hospedados no GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "Como este site é diferente de outros sites de consulta de IP?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "A maior diferença é que nosso banco de dados inclui informações relacionadas a proxy, e a inteligência de proxy que fornecemos é altamente precisa. Comparamos internamente com bancos de dados comerciais de detecção de proxy como o IPinfo; a precisão do nosso banco de dados é totalmente comparável às soluções comerciais, e é ainda mais precisa na detecção de proxies residenciais."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "Em segundo lugar, nossa cobertura geográfica para IPv6 é mais abrangente porque mesclamos vários bancos de dados para fornecer dados mais completos."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Nossa experiência de usuário também é melhor do que qualquer site de consulta de IP no mercado. O site funciona sem nenhum JavaScript, e carregamos apenas uma quantidade mínima de recursos externos (fontes, ícones), tornando a velocidade de carregamento inicial extremamente rápida."
msgid "Is this service stable?"
msgstr "Este serviço é estável?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "A lógica de negócio deste serviço é escrita inteiramente em Rust, oferecendo excelente desempenho que garante que o site permaneça operacional mesmo sob ataques DDoS L7. O site roda em um VPS com 2 núcleos e 2 GB de RAM, o que já é mais do que suficiente para esta configuração -- a menos que enfrentemos um ataque DDoS L7 massivo, embora tenhamos o Cloudflare na nossa frente."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Manter este site me custa apenas $18,00 por mês. Como o custo é tão baixo, não interromperei as operações devido a custos de hardware no futuro previsível, nem adicionarei qualquer publicidade."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Fique tranquilo, este serviço definitivamente não vai dar \"Rug Pull.\""
msgid "How can I sponsor this service?"
msgstr "Como posso patrocinar este serviço?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "Aceito doações em criptomoedas. Se você gostaria de patrocinar, entre em contato:"
msgid "Website source code licensed under"
msgstr "Código-fonte do site licenciado sob"
msgid "Website Source"
msgstr "Código-fonte do Site"
msgid "IP Database"
msgstr "Banco de Dados IP"
msgid "IP data provided by:"
msgstr "Dados de IP fornecidos por:"
msgid "Error"
msgstr "Erro"
msgid "Go to Home"
msgstr "Ir para a Página Inicial"
msgid "IP address information"
msgstr "Informações do endereço IP"
msgid "Frequently Asked Questions"
msgstr "Perguntas Frequentes"
msgid "Project links"
msgstr "Links do projeto"
msgid "Language"
msgstr "Idioma"
msgid "The page you are looking for does not exist."
msgstr "A página que você está procurando não existe."
msgid "IP not found in database"
msgstr "IP não encontrado no banco de dados"
msgid "Invalid IP address"
msgstr "Endereço IP inválido"
msgid "Missing client IP address"
msgstr "Endereço IP do cliente ausente"
msgid "Database lookup failed"
msgstr "Falha na consulta ao banco de dados"
msgid "Template render failed"
msgstr "Falha na renderização do template"
+187
View File
@@ -0,0 +1,187 @@
# Russian translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "Какой у меня IP-адрес?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "Мгновенно узнайте свой публичный IP-адрес. Определите геолокацию, ASN, организацию, а также обнаружение прокси/VPN/Tor для любого IP-адреса."
msgid "Search IP address"
msgstr "Поиск IP-адреса"
msgid "Type to search IP data"
msgstr "Введите для поиска данных IP"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "Организация"
msgid "Country"
msgstr "Страна"
msgid "City"
msgstr "Город"
msgid "Proxy"
msgstr "Прокси"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "Хостинг"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "Да"
msgid "No"
msgstr "Нет"
msgid "FAQ"
msgstr "Вопросы и ответы"
msgid "How to use this service from the command line"
msgstr "Как использовать этот сервис из командной строки"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "Вы можете использовать различные инструменты командной строки, такие как curl, httpie и wget. Рассмотрим наиболее популярный из них -- curl -- в качестве примера:"
msgid "Query current IP:"
msgstr "Запрос текущего IP:"
msgid "Query information for your own IP:"
msgstr "Запрос информации о вашем IP:"
msgid "Or display all information in JSON format:"
msgstr "Или отобразить всю информацию в формате JSON:"
msgid "Query information for a specific IP:"
msgstr "Запрос информации о конкретном IP:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "Как принудительно отобразить мой IPv4 или IPv6 адрес?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "Вы можете использовать <code>curl -4</code> или <code>curl -6</code> для просмотра только вашего IPv4 или IPv6 адреса соответственно."
msgid "How to get a JSON format response?"
msgstr "Как получить ответ в формате JSON?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "Вы можете отправить запрос на эндпоинт <code>/json</code> или отправить запрос с заголовком <code>application/json</code>."
msgid "Can I automate calls to your service via programs?"
msgstr "Могу ли я автоматизировать вызовы вашего сервиса через программы?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "Конечно, при условии соблюдения ограничения частоты запросов. В обычных условиях ограничение составляет 30 RPM (запросов в минуту); при высокой нагрузке мы увеличим ограничение до 10-5 RPM."
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "Наш API подходит только для ручных вызовов или небольших проектов. Если ваш сайт использует наш API для запроса IP-адресов посетителей, убедитесь, что вы используете очередь сообщений для отправки запросов во избежание блокировки. Если ваш проект имеет высокий трафик или чувствителен к задержкам, используйте нашу офлайн-базу данных с открытым исходным кодом: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, которая является той же базой данных, что используется в этом проекте."
msgid "Where does the IP data come from?"
msgstr "Откуда берутся данные об IP?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "Географические данные IP в основном поступают из бесплатных баз данных MaxMind и DB-IP; данные AS -- из бесплатной базы данных IPinfo; а данные о прокси IP -- из моей собственной базы данных <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a>."
msgid "Data Attributions:"
msgstr "Источники данных:"
msgid "Can I run my own service?"
msgstr "Могу ли я запустить собственный сервис?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "Да, исходный код и база данных этого проекта имеют открытый исходный код и размещены на GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "Чем этот сайт отличается от других сервисов поиска IP?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "Главное отличие в том, что наша база данных включает информацию о прокси, и предоставляемые нами данные о прокси отличаются высокой точностью. Мы провели внутреннее сравнение с коммерческими базами данных обнаружения прокси, такими как IPinfo; точность нашей базы данных полностью сопоставима с коммерческими, а в обнаружении резидентных прокси она даже точнее."
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "Кроме того, наше географическое покрытие для IPv6 более полное, поскольку мы объединяем несколько баз данных для предоставления более исчерпывающей информации."
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "Наш UX также лучше, чем у любого сервиса поиска IP на рынке. Сайт работает без JavaScript, и мы загружаем минимальное количество внешних ресурсов (шрифты, иконки), что делает начальную загрузку чрезвычайно быстрой."
msgid "Is this service stable?"
msgstr "Стабилен ли этот сервис?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "Бизнес-логика этого сервиса полностью написана на Rust, что обеспечивает отличную производительность и гарантирует работоспособность сайта даже при L7 DDoS-атаках. Сайт работает на VPS с 2 ядрами и 2 ГБ оперативной памяти, что уже избыточно для этой конфигурации -- если только мы не столкнёмся с массированной L7 DDoS-атакой, хотя перед нами стоит Cloudflare."
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "Содержание этого сайта обходится мне всего в $18.00 в месяц. Поскольку стоимость настолько низкая, в обозримом будущем я не прекращу работу из-за затрат на оборудование и не буду добавлять рекламу."
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "Будьте уверены, этот сервис точно не совершит \"Rug Pull.\""
msgid "How can I sponsor this service?"
msgstr "Как я могу поддержать этот сервис?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "Я принимаю пожертвования в криптовалюте. Если вы хотите поддержать проект, свяжитесь со мной:"
msgid "Website source code licensed under"
msgstr "Исходный код сайта лицензирован под"
msgid "Website Source"
msgstr "Исходный код сайта"
msgid "IP Database"
msgstr "База данных IP"
msgid "IP data provided by:"
msgstr "Данные IP предоставлены:"
msgid "Error"
msgstr "Ошибка"
msgid "Go to Home"
msgstr "На главную"
msgid "IP address information"
msgstr "Информация об IP-адресе"
msgid "Frequently Asked Questions"
msgstr "Часто задаваемые вопросы"
msgid "Project links"
msgstr "Ссылки проекта"
msgid "Language"
msgstr "Язык"
msgid "The page you are looking for does not exist."
msgstr "Запрашиваемая страница не существует."
msgid "IP not found in database"
msgstr "IP не найден в базе данных"
msgid "Invalid IP address"
msgstr "Недействительный IP-адрес"
msgid "Missing client IP address"
msgstr "Отсутствует IP-адрес клиента"
msgid "Database lookup failed"
msgstr "Ошибка поиска в базе данных"
msgid "Template render failed"
msgstr "Ошибка рендеринга шаблона"
+187
View File
@@ -0,0 +1,187 @@
# Simplified Chinese translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: zh-Hans\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "我的 IP 地址是什么?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "即时查询您的公网 IP 地址。查找任意 IP 地址的地理位置、ASN、组织以及代理/VPN/Tor 检测信息。"
msgid "Search IP address"
msgstr "搜索 IP 地址"
msgid "Type to search IP data"
msgstr "输入以搜索 IP 数据"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "组织"
msgid "Country"
msgstr "国家"
msgid "City"
msgstr "城市"
msgid "Proxy"
msgstr "代理"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "托管"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "是"
msgid "No"
msgstr "否"
msgid "FAQ"
msgstr "常见问题"
msgid "How to use this service from the command line"
msgstr "如何从命令行使用本服务"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "您可以使用 curl、httpie、wget 等各种命令行工具来使用本服务。以最常用的 curl 为例:"
msgid "Query current IP:"
msgstr "查询当前 IP:"
msgid "Query information for your own IP:"
msgstr "查询您自己的 IP 信息:"
msgid "Or display all information in JSON format:"
msgstr "或以 JSON 格式显示所有信息:"
msgid "Query information for a specific IP:"
msgstr "查询指定 IP 的信息:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "如何强制显示我的 IPv4 或 IPv6 地址?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "您可以使用 <code>curl -4</code> 或 <code>curl -6</code> 来分别仅查看您的 IPv4 或 IPv6 地址。"
msgid "How to get a JSON format response?"
msgstr "如何获取 JSON 格式的响应?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "您可以向 <code>/json</code> 端点发送请求,或发送带有 <code>application/json</code> 头的请求。"
msgid "Can I automate calls to your service via programs?"
msgstr "我可以通过程序自动调用你们的服务吗?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "当然可以,只要遵守速率限制即可。正常情况下,速率限制为 30 RPM(每分钟请求数);在高负载情况下,我们会将限制提高到 10-5 RPM。"
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "我们的 API 仅适用于手动调用或小规模项目。如果您的网站使用我们的 API 查询访客 IP,请确保使用消息队列发送请求以避免阻塞。如果您的项目流量较大或对延迟敏感,请使用我们的开源离线数据库:<a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>,这与本项目使用的是同一个数据库。"
msgid "Where does the IP data come from?"
msgstr "IP 数据来自哪里?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "IP 地理数据主要来自 MaxMind 和 DB-IP 的免费数据库;AS 数据来自 IPinfo 的免费数据库;IP 代理数据来自我自己的 <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> 数据库。"
msgid "Data Attributions:"
msgstr "数据来源:"
msgid "Can I run my own service?"
msgstr "我可以自己搭建这个服务吗?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "可以,本项目的源代码和数据库均为开源,托管在 GitHub 上:"
msgid "How is this website different from other IP lookup sites?"
msgstr "这个网站与其他 IP 查询网站有什么不同?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "最大的区别在于我们的数据库包含代理相关信息,并且我们提供的代理情报非常准确。我们曾私下将其与 IPinfo 等商业代理检测数据库进行对比;我们数据库的精度完全可以与商业数据库媲美,在检测住宅代理方面甚至更为准确。"
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "其次,我们对 IPv6 的地理覆盖更加全面,因为我们合并了多个数据库以提供更完整的数据。"
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "我们的用户体验也优于市面上任何 IP 查询网站。本站无需任何 JavaScript 即可运行,并且仅加载极少量的外部资源(字体、图标),使得首次加载速度极快。"
msgid "Is this service stable?"
msgstr "这个服务稳定吗?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "本服务的业务逻辑完全使用 Rust 编写,提供出色的性能,确保即使在 L7 DDoS 攻击下网站仍能正常运行。本站运行在一台拥有 2 核 2 GB 内存的 VPS 上,对于这个配置来说已经绰绰有余——除非遭遇大规模 L7 DDoS 攻击,不过我们前面有 Cloudflare 保护。"
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "运营这个网站每月仅花费我 $18.00。由于成本如此之低,在可预见的未来我不会因硬件成本而停止运营,也不会添加任何广告。"
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "请放心,本服务绝对不会\"跑路\"。"
msgid "How can I sponsor this service?"
msgstr "我如何赞助这个服务?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "我接受加密货币捐赠。如果您愿意赞助,请联系:"
msgid "Website source code licensed under"
msgstr "网站源代码基于以下许可证发布"
msgid "Website Source"
msgstr "网站源码"
msgid "IP Database"
msgstr "IP 数据库"
msgid "IP data provided by:"
msgstr "IP 数据由以下提供:"
msgid "Error"
msgstr "错误"
msgid "Go to Home"
msgstr "返回首页"
msgid "IP address information"
msgstr "IP 地址信息"
msgid "Frequently Asked Questions"
msgstr "常见问题"
msgid "Project links"
msgstr "项目链接"
msgid "Language"
msgstr "语言"
msgid "The page you are looking for does not exist."
msgstr "您要查找的页面不存在。"
msgid "IP not found in database"
msgstr "在数据库中未找到该 IP"
msgid "Invalid IP address"
msgstr "无效的 IP 地址"
msgid "Missing client IP address"
msgstr "缺少客户端 IP 地址"
msgid "Database lookup failed"
msgstr "数据库查询失败"
msgid "Template render failed"
msgstr "模板渲染失败"
+187
View File
@@ -0,0 +1,187 @@
# Traditional Chinese translations for RustyIP
# Copyright (C) RustyIP Contributors
# This file is distributed under the AGPL-3.0 license.
#
msgid ""
msgstr ""
"Language: zh-Hant\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "What is my IP address?"
msgstr "我的 IP 位址是什麼?"
msgid "Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address."
msgstr "即時查詢您的公開 IP 位址。查詢任何 IP 位址的地理位置、ASN、組織,以及代理/VPN/Tor 偵測資訊。"
msgid "Search IP address"
msgstr "搜尋 IP 位址"
msgid "Type to search IP data"
msgstr "輸入以搜尋 IP 資料"
msgid "ASN"
msgstr "ASN"
msgid "Org"
msgstr "組織"
msgid "Country"
msgstr "國家"
msgid "City"
msgstr "城市"
msgid "Proxy"
msgstr "代理"
msgid "VPN"
msgstr "VPN"
msgid "Hosting"
msgstr "託管"
msgid "Tor"
msgstr "Tor"
msgid "Yes"
msgstr "是"
msgid "No"
msgstr "否"
msgid "FAQ"
msgstr "常見問題"
msgid "How to use this service from the command line"
msgstr "如何從命令列使用此服務"
msgid "You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:"
msgstr "您可以使用各種命令列工具,例如 curl、httpie 和 wget 來使用此服務。以最常用的 curl 為例:"
msgid "Query current IP:"
msgstr "查詢目前 IP:"
msgid "Query information for your own IP:"
msgstr "查詢您自己的 IP 資訊:"
msgid "Or display all information in JSON format:"
msgstr "或以 JSON 格式顯示所有資訊:"
msgid "Query information for a specific IP:"
msgstr "查詢特定 IP 的資訊:"
msgid "How to force the display of my IPv4 or IPv6 address?"
msgstr "如何強制顯示我的 IPv4 或 IPv6 位址?"
msgid "You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively."
msgstr "您可以使用 <code>curl -4</code> 或 <code>curl -6</code> 來分別僅查看您的 IPv4 或 IPv6 位址。"
msgid "How to get a JSON format response?"
msgstr "如何取得 JSON 格式的回應?"
msgid "You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header."
msgstr "您可以向 <code>/json</code> 端點發送請求,或發送帶有 <code>application/json</code> 標頭的請求。"
msgid "Can I automate calls to your service via programs?"
msgstr "我可以透過程式自動呼叫你們的服務嗎?"
msgid "Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM."
msgstr "當然可以,只要遵守速率限制即可。正常情況下,速率限制為 30 RPM(每分鐘請求數);在高負載情況下,我們會將限制提高至 10-5 RPM。"
msgid "Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project."
msgstr "我們的 API 僅適用於手動呼叫或小型專案。如果您的網站使用我們的 API 查詢訪客 IP,請確保使用訊息佇列來發送請求以避免阻塞。如果您的專案流量較高或對延遲敏感,請使用我們的開源離線資料庫:<a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>,這與本專案使用的資料庫相同。"
msgid "Where does the IP data come from?"
msgstr "IP 資料從哪裡來?"
msgid "IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database."
msgstr "IP 地理資料主要來自 MaxMind 和 DB-IP 的免費資料庫;AS 資料來自 IPinfo 的免費資料庫;IP 代理資料則來自我自己的 <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> 資料庫。"
msgid "Data Attributions:"
msgstr "資料來源聲明:"
msgid "Can I run my own service?"
msgstr "我可以自行架設此服務嗎?"
msgid "Yes, the source code and database for this project are both open-source and hosted on GitHub:"
msgstr "可以,本專案的原始碼和資料庫皆為開源並託管於 GitHub:"
msgid "How is this website different from other IP lookup sites?"
msgstr "這個網站與其他 IP 查詢網站有什麼不同?"
msgid "The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies."
msgstr "最大的區別在於我們的資料庫包含代理相關資訊,且我們提供的代理情報非常準確。我們曾私下與 IPinfo 等商業代理偵測資料庫進行比較;我們資料庫的精確度完全可與商業資料庫媲美,在偵測住宅代理方面甚至更為準確。"
msgid "Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data."
msgstr "其次,我們對 IPv6 的地理覆蓋範圍更為全面,因為我們合併了多個資料庫以提供更完整的資料。"
msgid "Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast."
msgstr "我們的使用者體驗也優於市面上任何 IP 查詢網站。本網站無需任何 JavaScript 即可運行,我們僅載入極少量的外部資源(字型、圖示),使得首次載入速度極快。"
msgid "Is this service stable?"
msgstr "此服務穩定嗎?"
msgid "The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us."
msgstr "此服務的業務邏輯完全以 Rust 編寫,提供卓越的效能,確保網站即使在 L7 DDoS 攻擊下仍能正常運作。網站運行在一台具有 2 核心和 2 GB 記憶體的 VPS 上,對於此架構來說已經綽綽有餘——除非我們遭遇大規模 L7 DDoS 攻擊,不過我們前方有 Cloudflare 保護。"
msgid "Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements."
msgstr "運行此網站每月僅需 $18.00 美元。由於成本極低,在可預見的未來,我不會因硬體成本而停止營運,也不會加入任何廣告。"
msgid "Rest assured, this service will definitely not \"Rug Pull.\""
msgstr "請放心,此服務絕對不會「Rug Pull」。"
msgid "How can I sponsor this service?"
msgstr "我該如何贊助此服務?"
msgid "I accept cryptocurrency donations. If you would like to sponsor, please contact:"
msgstr "我接受加密貨幣捐贈。如果您想贊助,請聯繫:"
msgid "Website source code licensed under"
msgstr "網站原始碼授權條款"
msgid "Website Source"
msgstr "網站原始碼"
msgid "IP Database"
msgstr "IP 資料庫"
msgid "IP data provided by:"
msgstr "IP 資料由以下提供:"
msgid "Error"
msgstr "錯誤"
msgid "Go to Home"
msgstr "返回首頁"
msgid "IP address information"
msgstr "IP 位址資訊"
msgid "Frequently Asked Questions"
msgstr "常見問題"
msgid "Project links"
msgstr "專案連結"
msgid "Language"
msgstr "語言"
msgid "The page you are looking for does not exist."
msgstr "您所尋找的頁面不存在。"
msgid "IP not found in database"
msgstr "在資料庫中找不到此 IP"
msgid "Invalid IP address"
msgstr "無效的 IP 位址"
msgid "Missing client IP address"
msgstr "缺少用戶端 IP 位址"
msgid "Database lookup failed"
msgstr "資料庫查詢失敗"
msgid "Template render failed"
msgstr "範本渲染失敗"
+436 -36
View File
@@ -2,14 +2,15 @@ use std::net::IpAddr;
use std::sync::Arc;
use askama::Template;
use axum::extract::{Query, State};
use axum::extract::{Path, Query, State};
use axum::http::{HeaderMap, StatusCode, header};
use axum::response::{Html, IntoResponse, Response};
use axum::response::{Html, IntoResponse, Redirect, Response};
use crate::cli_detect::is_cli_user_agent;
use crate::db::{self, SharedDb};
use crate::error::AppError;
use crate::models::{IpInfo, get_en_name};
use crate::i18n::{self, Locale};
use crate::models::{IpInfo, get_localized_name};
use crate::static_assets;
#[derive(serde::Deserialize)]
@@ -42,6 +43,61 @@ struct IndexTemplate {
is_vpn: bool,
is_hosting: bool,
is_tor: bool,
locale: Locale,
lang_tag: &'static str,
html_dir: &'static str,
t_title: &'static str,
t_description: &'static str,
t_search_label: &'static str,
t_search_placeholder: &'static str,
t_asn: &'static str,
t_org: &'static str,
t_country: &'static str,
t_city: &'static str,
t_proxy: &'static str,
t_vpn: &'static str,
t_hosting: &'static str,
t_tor: &'static str,
t_yes: &'static str,
t_no: &'static str,
t_faq: &'static str,
t_faq_cli_q: &'static str,
t_faq_cli_a: &'static str,
t_faq_cli_query_current: &'static str,
t_faq_cli_query_own: &'static str,
t_faq_cli_json: &'static str,
t_faq_cli_specific: &'static str,
t_faq_ipv46_q: &'static str,
t_faq_ipv46_a: &'static str,
t_faq_json_q: &'static str,
t_faq_json_a: &'static str,
t_faq_automate_q: &'static str,
t_faq_automate_a1: &'static str,
t_faq_automate_a2: &'static str,
t_faq_data_q: &'static str,
t_faq_data_a: &'static str,
t_faq_data_attr: &'static str,
t_faq_selfhost_q: &'static str,
t_faq_selfhost_a: &'static str,
t_faq_diff_q: &'static str,
t_faq_diff_a1: &'static str,
t_faq_diff_a2: &'static str,
t_faq_diff_a3: &'static str,
t_faq_stable_q: &'static str,
t_faq_stable_a1: &'static str,
t_faq_stable_a2: &'static str,
t_faq_stable_a3: &'static str,
t_faq_sponsor_q: &'static str,
t_faq_sponsor_a: &'static str,
t_footer_license: &'static str,
t_footer_source: &'static str,
t_footer_db: &'static str,
t_footer_attribution: &'static str,
t_table_aria: &'static str,
t_faq_aria: &'static str,
t_nav_aria: &'static str,
t_language: &'static str,
all_locales: &'static [Locale],
}
#[derive(Template)]
@@ -51,6 +107,18 @@ struct ErrorAlertTemplate {
css_version: u64,
query: String,
error_message: String,
locale: Locale,
lang_tag: &'static str,
html_dir: &'static str,
t_error: &'static str,
t_search_label: &'static str,
t_search_placeholder: &'static str,
t_footer_license: &'static str,
t_footer_source: &'static str,
t_footer_db: &'static str,
t_nav_aria: &'static str,
t_language: &'static str,
all_locales: &'static [Locale],
}
#[derive(Template)]
@@ -61,6 +129,10 @@ struct ErrorPageTemplate {
status_code: u16,
status_text: String,
error_message: String,
lang_tag: &'static str,
html_dir: &'static str,
t_go_home: &'static str,
locale: Locale,
}
fn resolve_ip(headers: &HeaderMap, query: &IpQuery, dev_mode: bool) -> Result<IpAddr, AppError> {
@@ -116,26 +188,208 @@ fn format_org(info: &IpInfo) -> &str {
.unwrap_or_default()
}
fn format_country(info: &IpInfo) -> &str {
fn format_country(info: &IpInfo, locale: Locale) -> &str {
info.country
.as_ref()
.map(|c| get_en_name(&c.names))
.map(|c| get_localized_name(&c.names, locale.mmdb_key()))
.unwrap_or_default()
}
fn format_city(info: &IpInfo) -> &str {
fn format_city(info: &IpInfo, locale: Locale) -> &str {
info.city
.as_ref()
.map(|c| get_en_name(&c.names))
.map(|c| get_localized_name(&c.names, locale.mmdb_key()))
.unwrap_or_default()
}
fn render_error_alert(state: &AppState, query: &str, error: &AppError) -> Response {
fn build_translations(locale: Locale) -> Translations {
Translations {
t_title: i18n::translate(locale, "What is my IP address?"),
t_description: i18n::translate(
locale,
"Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address.",
),
t_search_label: i18n::translate(locale, "Search IP address"),
t_search_placeholder: i18n::translate(locale, "Type to search IP data"),
t_asn: i18n::translate(locale, "ASN"),
t_org: i18n::translate(locale, "Org"),
t_country: i18n::translate(locale, "Country"),
t_city: i18n::translate(locale, "City"),
t_proxy: i18n::translate(locale, "Proxy"),
t_vpn: i18n::translate(locale, "VPN"),
t_hosting: i18n::translate(locale, "Hosting"),
t_tor: i18n::translate(locale, "Tor"),
t_yes: i18n::translate(locale, "Yes"),
t_no: i18n::translate(locale, "No"),
t_faq: i18n::translate(locale, "FAQ"),
t_faq_cli_q: i18n::translate(locale, "How to use this service from the command line"),
t_faq_cli_a: i18n::translate(
locale,
"You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:",
),
t_faq_cli_query_current: i18n::translate(locale, "Query current IP:"),
t_faq_cli_query_own: i18n::translate(locale, "Query information for your own IP:"),
t_faq_cli_json: i18n::translate(locale, "Or display all information in JSON format:"),
t_faq_cli_specific: i18n::translate(locale, "Query information for a specific IP:"),
t_faq_ipv46_q: i18n::translate(
locale,
"How to force the display of my IPv4 or IPv6 address?",
),
t_faq_ipv46_a: i18n::translate(
locale,
"You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively.",
),
t_faq_json_q: i18n::translate(locale, "How to get a JSON format response?"),
t_faq_json_a: i18n::translate(
locale,
"You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header.",
),
t_faq_automate_q: i18n::translate(
locale,
"Can I automate calls to your service via programs?",
),
t_faq_automate_a1: i18n::translate(
locale,
"Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM.",
),
t_faq_automate_a2: i18n::translate(
locale,
"Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href=\"https://github.com/NetworkCats/Merged-IP-Data\">Merged IP Database</a>, which is the same database used by this project.",
),
t_faq_data_q: i18n::translate(locale, "Where does the IP data come from?"),
t_faq_data_a: i18n::translate(
locale,
"IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href=\"https://github.com/NetworkCats/OpenProxyDB\">OpenProxyDB</a> database.",
),
t_faq_data_attr: i18n::translate(locale, "Data Attributions:"),
t_faq_selfhost_q: i18n::translate(locale, "Can I run my own service?"),
t_faq_selfhost_a: i18n::translate(
locale,
"Yes, the source code and database for this project are both open-source and hosted on GitHub:",
),
t_faq_diff_q: i18n::translate(
locale,
"How is this website different from other IP lookup sites?",
),
t_faq_diff_a1: i18n::translate(
locale,
"The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies.",
),
t_faq_diff_a2: i18n::translate(
locale,
"Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data.",
),
t_faq_diff_a3: i18n::translate(
locale,
"Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast.",
),
t_faq_stable_q: i18n::translate(locale, "Is this service stable?"),
t_faq_stable_a1: i18n::translate(
locale,
"The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us.",
),
t_faq_stable_a2: i18n::translate(
locale,
"Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements.",
),
t_faq_stable_a3: i18n::translate(
locale,
"Rest assured, this service will definitely not \"Rug Pull.\"",
),
t_faq_sponsor_q: i18n::translate(locale, "How can I sponsor this service?"),
t_faq_sponsor_a: i18n::translate(
locale,
"I accept cryptocurrency donations. If you would like to sponsor, please contact:",
),
t_footer_license: i18n::translate(locale, "Website source code licensed under"),
t_footer_source: i18n::translate(locale, "Website Source"),
t_footer_db: i18n::translate(locale, "IP Database"),
t_footer_attribution: i18n::translate(locale, "IP data provided by:"),
t_error: i18n::translate(locale, "Error"),
t_go_home: i18n::translate(locale, "Go to Home"),
t_table_aria: i18n::translate(locale, "IP address information"),
t_faq_aria: i18n::translate(locale, "Frequently Asked Questions"),
t_nav_aria: i18n::translate(locale, "Project links"),
t_language: i18n::translate(locale, "Language"),
}
}
struct Translations {
t_title: &'static str,
t_description: &'static str,
t_search_label: &'static str,
t_search_placeholder: &'static str,
t_asn: &'static str,
t_org: &'static str,
t_country: &'static str,
t_city: &'static str,
t_proxy: &'static str,
t_vpn: &'static str,
t_hosting: &'static str,
t_tor: &'static str,
t_yes: &'static str,
t_no: &'static str,
t_faq: &'static str,
t_faq_cli_q: &'static str,
t_faq_cli_a: &'static str,
t_faq_cli_query_current: &'static str,
t_faq_cli_query_own: &'static str,
t_faq_cli_json: &'static str,
t_faq_cli_specific: &'static str,
t_faq_ipv46_q: &'static str,
t_faq_ipv46_a: &'static str,
t_faq_json_q: &'static str,
t_faq_json_a: &'static str,
t_faq_automate_q: &'static str,
t_faq_automate_a1: &'static str,
t_faq_automate_a2: &'static str,
t_faq_data_q: &'static str,
t_faq_data_a: &'static str,
t_faq_data_attr: &'static str,
t_faq_selfhost_q: &'static str,
t_faq_selfhost_a: &'static str,
t_faq_diff_q: &'static str,
t_faq_diff_a1: &'static str,
t_faq_diff_a2: &'static str,
t_faq_diff_a3: &'static str,
t_faq_stable_q: &'static str,
t_faq_stable_a1: &'static str,
t_faq_stable_a2: &'static str,
t_faq_stable_a3: &'static str,
t_faq_sponsor_q: &'static str,
t_faq_sponsor_a: &'static str,
t_footer_license: &'static str,
t_footer_source: &'static str,
t_footer_db: &'static str,
t_footer_attribution: &'static str,
t_error: &'static str,
t_go_home: &'static str,
t_table_aria: &'static str,
t_faq_aria: &'static str,
t_nav_aria: &'static str,
t_language: &'static str,
}
fn render_error_alert(state: &AppState, locale: Locale, query: &str, error: &AppError) -> Response {
let t = build_translations(locale);
let error_msg = i18n::translate(locale, error.message());
let template = ErrorAlertTemplate {
site_domain: state.site_domain.clone(),
css_version: static_assets::asset_version("style.css"),
query: query.to_owned(),
error_message: error.message().to_owned(),
error_message: error_msg.to_owned(),
locale,
lang_tag: locale.tag(),
html_dir: locale.html_dir(),
t_error: t.t_error,
t_search_label: t.t_search_label,
t_search_placeholder: t.t_search_placeholder,
t_footer_license: t.t_footer_license,
t_footer_source: t.t_footer_source,
t_footer_db: t.t_footer_db,
t_nav_aria: t.t_nav_aria,
t_language: t.t_language,
all_locales: Locale::ALL,
};
let status = error.status_code();
match template.render() {
@@ -144,13 +398,24 @@ fn render_error_alert(state: &AppState, query: &str, error: &AppError) -> Respon
}
}
fn render_error_page(site_domain: &Arc<str>, status: StatusCode, message: &str) -> Response {
fn render_error_page(
site_domain: &Arc<str>,
locale: Locale,
status: StatusCode,
message: &str,
) -> Response {
let t = build_translations(locale);
let localized_message = i18n::translate(locale, message);
let template = ErrorPageTemplate {
site_domain: site_domain.clone(),
css_version: static_assets::asset_version("style.css"),
status_code: status.as_u16(),
status_text: status.canonical_reason().unwrap_or("Error").to_owned(),
error_message: message.to_owned(),
error_message: localized_message.to_owned(),
lang_tag: locale.tag(),
html_dir: locale.html_dir(),
t_go_home: t.t_go_home,
locale,
};
match template.render() {
Ok(html) => (status, Html(html)).into_response(),
@@ -158,11 +423,62 @@ fn render_error_page(site_domain: &Arc<str>, status: StatusCode, message: &str)
}
}
pub async fn root(
pub async fn root_redirect(
State(state): State<AppState>,
headers: HeaderMap,
Query(query): Query<IpQuery>,
) -> Response {
let ua = headers
.get("User-Agent")
.and_then(|v| v.to_str().ok())
.unwrap_or("");
// CLI clients and JSON requests bypass i18n entirely.
if is_cli_user_agent(ua) && query.ip.is_none() {
return match extract_client_ip(&headers, state.dev_mode) {
Ok(ip) => format!("{ip}\n").into_response(),
Err(e) => e.into_response(),
};
}
if wants_json(&headers) && query.ip.is_none() {
return match extract_client_ip(&headers, state.dev_mode)
.and_then(|ip| lookup_ip(&state.db, ip))
{
Ok(info) => axum::Json(info).into_response(),
Err(e) => e.into_response(),
};
}
let locale = i18n::negotiate_locale(&headers);
let mut redirect_path = format!("/{}", locale.tag());
if let Some(ref ip) = query.ip {
redirect_path.push_str("?ip=");
redirect_path.push_str(&urlencoding::encode(ip));
}
Redirect::temporary(&redirect_path).into_response()
}
pub async fn root(
State(state): State<AppState>,
Path(lang): Path<String>,
headers: HeaderMap,
Query(query): Query<IpQuery>,
) -> Response {
let locale = match Locale::from_tag(&lang) {
Some(l) => l,
None => {
return render_error_page(
&state.site_domain,
Locale::En,
StatusCode::NOT_FOUND,
"The page you are looking for does not exist.",
);
}
};
let ua = headers
.get("User-Agent")
.and_then(|v| v.to_str().ok())
@@ -188,14 +504,16 @@ pub async fn root(
let ip = match resolve_ip(&headers, &query, state.dev_mode) {
Ok(ip) => ip,
Err(e) => return render_error_alert(&state, &query_str, &e),
Err(e) => return render_error_alert(&state, locale, &query_str, &e),
};
let info = match lookup_ip(&state.db, ip) {
Ok(info) => info,
Err(e) => return render_error_alert(&state, &query_str, &e),
Err(e) => return render_error_alert(&state, locale, &query_str, &e),
};
let t = build_translations(locale);
let template = IndexTemplate {
ip: info.ip.clone(),
query: query.ip.unwrap_or_default(),
@@ -203,17 +521,72 @@ pub async fn root(
css_version: static_assets::asset_version("style.css"),
asn: format_asn(&info),
org: format_org(&info).to_owned(),
country: format_country(&info).to_owned(),
city: format_city(&info).to_owned(),
country: format_country(&info, locale).to_owned(),
city: format_city(&info, locale).to_owned(),
is_proxy: info.proxy.is_proxy,
is_vpn: info.proxy.is_vpn,
is_hosting: info.proxy.is_hosting,
is_tor: info.proxy.is_tor,
locale,
lang_tag: locale.tag(),
html_dir: locale.html_dir(),
t_title: t.t_title,
t_description: t.t_description,
t_search_label: t.t_search_label,
t_search_placeholder: t.t_search_placeholder,
t_asn: t.t_asn,
t_org: t.t_org,
t_country: t.t_country,
t_city: t.t_city,
t_proxy: t.t_proxy,
t_vpn: t.t_vpn,
t_hosting: t.t_hosting,
t_tor: t.t_tor,
t_yes: t.t_yes,
t_no: t.t_no,
t_faq: t.t_faq,
t_faq_cli_q: t.t_faq_cli_q,
t_faq_cli_a: t.t_faq_cli_a,
t_faq_cli_query_current: t.t_faq_cli_query_current,
t_faq_cli_query_own: t.t_faq_cli_query_own,
t_faq_cli_json: t.t_faq_cli_json,
t_faq_cli_specific: t.t_faq_cli_specific,
t_faq_ipv46_q: t.t_faq_ipv46_q,
t_faq_ipv46_a: t.t_faq_ipv46_a,
t_faq_json_q: t.t_faq_json_q,
t_faq_json_a: t.t_faq_json_a,
t_faq_automate_q: t.t_faq_automate_q,
t_faq_automate_a1: t.t_faq_automate_a1,
t_faq_automate_a2: t.t_faq_automate_a2,
t_faq_data_q: t.t_faq_data_q,
t_faq_data_a: t.t_faq_data_a,
t_faq_data_attr: t.t_faq_data_attr,
t_faq_selfhost_q: t.t_faq_selfhost_q,
t_faq_selfhost_a: t.t_faq_selfhost_a,
t_faq_diff_q: t.t_faq_diff_q,
t_faq_diff_a1: t.t_faq_diff_a1,
t_faq_diff_a2: t.t_faq_diff_a2,
t_faq_diff_a3: t.t_faq_diff_a3,
t_faq_stable_q: t.t_faq_stable_q,
t_faq_stable_a1: t.t_faq_stable_a1,
t_faq_stable_a2: t.t_faq_stable_a2,
t_faq_stable_a3: t.t_faq_stable_a3,
t_faq_sponsor_q: t.t_faq_sponsor_q,
t_faq_sponsor_a: t.t_faq_sponsor_a,
t_footer_license: t.t_footer_license,
t_footer_source: t.t_footer_source,
t_footer_db: t.t_footer_db,
t_footer_attribution: t.t_footer_attribution,
t_table_aria: t.t_table_aria,
t_faq_aria: t.t_faq_aria,
t_nav_aria: t.t_nav_aria,
t_language: t.t_language,
all_locales: Locale::ALL,
};
match template.render() {
Ok(html) => Html(html).into_response(),
Err(_) => render_error_alert(&state, &query_str, &AppError::TemplateRenderFailed),
Err(_) => render_error_alert(&state, locale, &query_str, &AppError::TemplateRenderFailed),
}
}
@@ -350,21 +723,28 @@ pub fn build_openapi_json(site_domain: &str) -> Arc<str> {
}
pub async fn robots_txt(State(state): State<AppState>) -> Response {
let mut disallow = String::new();
for path in &[
"/json",
"/ip",
"/asn",
"/org",
"/country",
"/city",
"/proxy",
"/vpn",
"/hosting",
"/tor",
"/health",
"/openapi.json",
] {
disallow.push_str(&format!("Disallow: {path}\n"));
}
let body = format!(
"User-agent: *\n\
Allow: /\n\
Disallow: /json\n\
Disallow: /ip\n\
Disallow: /asn\n\
Disallow: /org\n\
Disallow: /country\n\
Disallow: /city\n\
Disallow: /proxy\n\
Disallow: /vpn\n\
Disallow: /hosting\n\
Disallow: /tor\n\
Disallow: /health\n\
Disallow: /openapi.json\n\
{disallow}\
\n\
Sitemap: https://{}/sitemap.xml\n",
state.site_domain
@@ -373,14 +753,32 @@ pub async fn robots_txt(State(state): State<AppState>) -> Response {
}
pub async fn sitemap_xml(State(state): State<AppState>) -> Response {
let mut urls = String::new();
let domain = &state.site_domain;
for locale in Locale::ALL {
let tag = locale.tag();
urls.push_str(&format!(
" <url>\n <loc>https://{domain}/{tag}/</loc>\n"
));
for alt in Locale::ALL {
let alt_tag = alt.tag();
urls.push_str(&format!(
" <xhtml:link rel=\"alternate\" hreflang=\"{alt_tag}\" href=\"https://{domain}/{alt_tag}/\"/>\n"
));
}
urls.push_str(
&format!(" <xhtml:link rel=\"alternate\" hreflang=\"x-default\" href=\"https://{domain}/\"/>\n"),
);
urls.push_str(" </url>\n");
}
let body = format!(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n\
\x20 <url>\n\
\x20 <loc>https://{}/</loc>\n\
\x20 </url>\n\
</urlset>\n",
state.site_domain
<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n\
\x20 xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n\
{urls}\
</urlset>\n"
);
(
[(header::CONTENT_TYPE, "application/xml; charset=utf-8")],
@@ -389,9 +787,11 @@ pub async fn sitemap_xml(State(state): State<AppState>) -> Response {
.into_response()
}
pub async fn not_found(State(state): State<AppState>) -> Response {
pub async fn not_found(State(state): State<AppState>, headers: HeaderMap) -> Response {
let locale = i18n::negotiate_locale(&headers);
render_error_page(
&state.site_domain,
locale,
StatusCode::NOT_FOUND,
"The page you are looking for does not exist.",
)
+455
View File
@@ -0,0 +1,455 @@
use std::collections::HashMap;
use std::sync::LazyLock;
use axum::http::HeaderMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Locale {
En,
Es,
De,
Ja,
Ko,
Id,
Fr,
Ru,
Pt,
It,
ZhHant,
ZhHans,
Nl,
Ar,
}
impl Locale {
pub const ALL: &[Locale] = &[
Locale::En,
Locale::Es,
Locale::De,
Locale::Ja,
Locale::Ko,
Locale::Id,
Locale::Fr,
Locale::Ru,
Locale::Pt,
Locale::It,
Locale::ZhHant,
Locale::ZhHans,
Locale::Nl,
Locale::Ar,
];
pub fn tag(self) -> &'static str {
match self {
Locale::En => "en",
Locale::Es => "es",
Locale::De => "de",
Locale::Ja => "ja",
Locale::Ko => "ko",
Locale::Id => "id",
Locale::Fr => "fr",
Locale::Ru => "ru",
Locale::Pt => "pt",
Locale::It => "it",
Locale::ZhHant => "zh-Hant",
Locale::ZhHans => "zh-Hans",
Locale::Nl => "nl",
Locale::Ar => "ar",
}
}
pub fn name(self) -> &'static str {
match self {
Locale::En => "English",
Locale::Es => "Espa\u{00f1}ol",
Locale::De => "Deutsch",
Locale::Ja => "\u{65e5}\u{672c}\u{8a9e}",
Locale::Ko => "\u{d55c}\u{ad6d}\u{c5b4}",
Locale::Id => "Bahasa Indonesia",
Locale::Fr => "Fran\u{00e7}ais",
Locale::Ru => "\u{0420}\u{0443}\u{0441}\u{0441}\u{043a}\u{0438}\u{0439}",
Locale::Pt => "Portugu\u{00ea}s",
Locale::It => "Italiano",
Locale::ZhHant => "\u{7e41}\u{9ad4}\u{4e2d}\u{6587}",
Locale::ZhHans => "\u{7b80}\u{4f53}\u{4e2d}\u{6587}",
Locale::Nl => "Nederlands",
Locale::Ar => "\u{0627}\u{0644}\u{0639}\u{0631}\u{0628}\u{064a}\u{0629}",
}
}
pub fn html_dir(self) -> &'static str {
match self {
Locale::Ar => "rtl",
_ => "ltr",
}
}
pub fn mmdb_key(self) -> &'static str {
match self {
Locale::En => "en",
Locale::Es => "es",
Locale::De => "de",
Locale::Ja => "ja",
Locale::Fr => "fr",
Locale::Ru => "ru",
Locale::Pt => "pt-BR",
Locale::ZhHans => "zh-CN",
// These languages are not in the MMDB; fall back to English.
Locale::Ko | Locale::Id | Locale::It | Locale::ZhHant | Locale::Nl | Locale::Ar => "en",
}
}
pub fn from_tag(tag: &str) -> Option<Locale> {
match tag {
"en" => Some(Locale::En),
"es" => Some(Locale::Es),
"de" => Some(Locale::De),
"ja" => Some(Locale::Ja),
"ko" => Some(Locale::Ko),
"id" => Some(Locale::Id),
"fr" => Some(Locale::Fr),
"ru" => Some(Locale::Ru),
"pt" => Some(Locale::Pt),
"it" => Some(Locale::It),
"zh-Hant" => Some(Locale::ZhHant),
"zh-Hans" => Some(Locale::ZhHans),
"nl" => Some(Locale::Nl),
"ar" => Some(Locale::Ar),
_ => None,
}
}
}
type Catalog = HashMap<&'static str, &'static str>;
struct Translations {
catalogs: HashMap<Locale, Catalog>,
}
impl Translations {
fn get<'a>(&self, locale: Locale, msgid: &'a str) -> &'a str {
self.catalogs
.get(&locale)
.and_then(|cat| cat.get(msgid).copied())
.unwrap_or(msgid)
}
}
fn parse_po(source: &'static str) -> Catalog {
let mut catalog = HashMap::new();
let mut current_msgid = String::new();
let mut current_msgstr = String::new();
let mut reading = Reading::None;
for line in source.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
if reading == Reading::Msgstr && !current_msgid.is_empty() {
insert_entry(&mut catalog, &current_msgid, &current_msgstr, source);
current_msgid.clear();
current_msgstr.clear();
}
reading = Reading::None;
continue;
}
if let Some(rest) = trimmed.strip_prefix("msgid ") {
if reading == Reading::Msgstr && !current_msgid.is_empty() {
insert_entry(&mut catalog, &current_msgid, &current_msgstr, source);
}
current_msgid.clear();
current_msgstr.clear();
append_quoted(&mut current_msgid, rest);
reading = Reading::Msgid;
} else if let Some(rest) = trimmed.strip_prefix("msgstr ") {
append_quoted(&mut current_msgstr, rest);
reading = Reading::Msgstr;
} else if trimmed.starts_with('"') {
match reading {
Reading::Msgid => append_quoted(&mut current_msgid, trimmed),
Reading::Msgstr => append_quoted(&mut current_msgstr, trimmed),
Reading::None => {}
}
}
}
if reading == Reading::Msgstr && !current_msgid.is_empty() {
insert_entry(&mut catalog, &current_msgid, &current_msgstr, source);
}
catalog
}
fn insert_entry(catalog: &mut Catalog, msgid: &str, msgstr: &str, source: &'static str) {
if msgstr.is_empty() {
return;
}
// Find the msgstr in the static source to get a &'static str reference.
// This avoids allocating owned Strings at runtime.
let id_static = find_in_static(source, msgid);
let str_static = find_in_static(source, msgstr);
if let (Some(id), Some(s)) = (id_static, str_static) {
catalog.insert(id, s);
}
}
fn find_in_static(source: &'static str, needle: &str) -> Option<&'static str> {
source
.find(needle)
.map(|pos| &source[pos..pos + needle.len()])
}
fn append_quoted(target: &mut String, quoted: &str) {
let s = quoted.trim();
if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
let inner = &s[1..s.len() - 1];
let unescaped = inner
.replace("\\n", "\n")
.replace("\\\"", "\"")
.replace("\\\\", "\\");
target.push_str(&unescaped);
}
}
#[derive(PartialEq)]
enum Reading {
None,
Msgid,
Msgstr,
}
static TRANSLATIONS: LazyLock<Translations> = LazyLock::new(|| {
let mut catalogs = HashMap::new();
catalogs.insert(Locale::En, parse_po(include_str!("../locales/en.po")));
catalogs.insert(Locale::Es, parse_po(include_str!("../locales/es.po")));
catalogs.insert(Locale::De, parse_po(include_str!("../locales/de.po")));
catalogs.insert(Locale::Ja, parse_po(include_str!("../locales/ja.po")));
catalogs.insert(Locale::Ko, parse_po(include_str!("../locales/ko.po")));
catalogs.insert(Locale::Id, parse_po(include_str!("../locales/id.po")));
catalogs.insert(Locale::Fr, parse_po(include_str!("../locales/fr.po")));
catalogs.insert(Locale::Ru, parse_po(include_str!("../locales/ru.po")));
catalogs.insert(Locale::Pt, parse_po(include_str!("../locales/pt.po")));
catalogs.insert(Locale::It, parse_po(include_str!("../locales/it.po")));
catalogs.insert(
Locale::ZhHant,
parse_po(include_str!("../locales/zh-Hant.po")),
);
catalogs.insert(
Locale::ZhHans,
parse_po(include_str!("../locales/zh-Hans.po")),
);
catalogs.insert(Locale::Nl, parse_po(include_str!("../locales/nl.po")));
catalogs.insert(Locale::Ar, parse_po(include_str!("../locales/ar.po")));
Translations { catalogs }
});
pub fn translate(locale: Locale, msgid: &str) -> &str {
TRANSLATIONS.get(locale, msgid)
}
pub fn negotiate_locale(headers: &HeaderMap) -> Locale {
let accept = match headers.get("Accept-Language").and_then(|v| v.to_str().ok()) {
Some(v) => v,
None => return Locale::En,
};
let mut candidates: Vec<(&str, f32)> = Vec::new();
for part in accept.split(',') {
let part = part.trim();
if let Some((lang, q_part)) = part.split_once(";q=") {
let quality: f32 = q_part.trim().parse().unwrap_or(0.0);
candidates.push((lang.trim(), quality));
} else {
candidates.push((part, 1.0));
}
}
candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
for (tag, _) in &candidates {
if let Some(locale) = match_language_tag(tag) {
return locale;
}
}
Locale::En
}
fn match_language_tag(tag: &str) -> Option<Locale> {
let tag_lower = tag.to_ascii_lowercase();
// Exact matches (case-insensitive)
match tag_lower.as_str() {
"zh-hant" | "zh-tw" | "zh-hk" | "zh-mo" => return Some(Locale::ZhHant),
"zh-hans" | "zh-cn" | "zh-sg" | "zh-my" => return Some(Locale::ZhHans),
"pt-br" | "pt" => return Some(Locale::Pt),
_ => {}
}
// Try primary subtag
let primary = tag_lower.split('-').next().unwrap_or(&tag_lower);
match primary {
"en" => Some(Locale::En),
"es" => Some(Locale::Es),
"de" => Some(Locale::De),
"ja" => Some(Locale::Ja),
"ko" => Some(Locale::Ko),
"id" | "ms" => Some(Locale::Id),
"fr" => Some(Locale::Fr),
"ru" => Some(Locale::Ru),
"pt" => Some(Locale::Pt),
"it" => Some(Locale::It),
"zh" => Some(Locale::ZhHans),
"nl" => Some(Locale::Nl),
"ar" => Some(Locale::Ar),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn locale_tag_roundtrip() {
for locale in Locale::ALL {
let tag = locale.tag();
let parsed = Locale::from_tag(tag).expect("tag should parse back");
assert_eq!(*locale, parsed);
}
}
#[test]
fn locale_names_are_non_empty() {
for locale in Locale::ALL {
assert!(!locale.name().is_empty());
}
}
#[test]
fn translate_english_returns_english() {
let result = translate(Locale::En, "FAQ");
assert_eq!(result, "FAQ");
}
#[test]
fn translate_unknown_msgid_returns_msgid() {
let result = translate(Locale::En, "this-does-not-exist");
assert_eq!(result, "this-does-not-exist");
}
#[test]
fn translate_non_english_returns_translated() {
let result = translate(Locale::Es, "Country");
assert!(!result.is_empty());
// Spanish for Country should not be "Country"
assert_ne!(result, "Country");
}
#[test]
fn negotiate_locale_empty_header() {
let headers = HeaderMap::new();
assert_eq!(negotiate_locale(&headers), Locale::En);
}
#[test]
fn negotiate_locale_japanese() {
let mut headers = HeaderMap::new();
headers.insert(
"Accept-Language",
"ja,en-US;q=0.9,en;q=0.8".parse().unwrap(),
);
assert_eq!(negotiate_locale(&headers), Locale::Ja);
}
#[test]
fn negotiate_locale_chinese_traditional() {
let mut headers = HeaderMap::new();
headers.insert(
"Accept-Language",
"zh-TW,zh;q=0.9,en;q=0.8".parse().unwrap(),
);
assert_eq!(negotiate_locale(&headers), Locale::ZhHant);
}
#[test]
fn negotiate_locale_chinese_simplified() {
let mut headers = HeaderMap::new();
headers.insert("Accept-Language", "zh-CN,zh;q=0.9".parse().unwrap());
assert_eq!(negotiate_locale(&headers), Locale::ZhHans);
}
#[test]
fn negotiate_locale_portuguese() {
let mut headers = HeaderMap::new();
headers.insert(
"Accept-Language",
"pt-BR,pt;q=0.9,en;q=0.8".parse().unwrap(),
);
assert_eq!(negotiate_locale(&headers), Locale::Pt);
}
#[test]
fn negotiate_locale_arabic() {
let mut headers = HeaderMap::new();
headers.insert("Accept-Language", "ar,en;q=0.5".parse().unwrap());
assert_eq!(negotiate_locale(&headers), Locale::Ar);
}
#[test]
fn negotiate_locale_quality_ordering() {
let mut headers = HeaderMap::new();
headers.insert(
"Accept-Language",
"en;q=0.5,de;q=0.9,fr;q=0.7".parse().unwrap(),
);
assert_eq!(negotiate_locale(&headers), Locale::De);
}
#[test]
fn negotiate_locale_unknown_falls_back_to_english() {
let mut headers = HeaderMap::new();
headers.insert("Accept-Language", "xx-YY,zz;q=0.5".parse().unwrap());
assert_eq!(negotiate_locale(&headers), Locale::En);
}
#[test]
fn arabic_is_rtl() {
assert_eq!(Locale::Ar.html_dir(), "rtl");
}
#[test]
fn english_is_ltr() {
assert_eq!(Locale::En.html_dir(), "ltr");
}
#[test]
fn mmdb_key_mapping() {
assert_eq!(Locale::En.mmdb_key(), "en");
assert_eq!(Locale::De.mmdb_key(), "de");
assert_eq!(Locale::Pt.mmdb_key(), "pt-BR");
assert_eq!(Locale::ZhHans.mmdb_key(), "zh-CN");
assert_eq!(Locale::Ko.mmdb_key(), "en");
}
#[test]
fn all_locales_have_translations() {
for locale in Locale::ALL {
let result = translate(*locale, "What is my IP address?");
assert!(
!result.is_empty(),
"locale {:?} has no translation for title",
locale
);
}
}
#[test]
fn parse_po_handles_multiline() {
// We cannot use parse_po directly with a non-static str for the
// static-reference optimization, but we can verify the PO files load.
let _ = translate(Locale::Es, "FAQ");
}
}
+1
View File
@@ -3,6 +3,7 @@ pub mod config;
pub mod db;
pub mod error;
pub mod handlers;
pub mod i18n;
pub mod middleware;
pub mod models;
pub mod routes;
+16
View File
@@ -424,6 +424,22 @@ pub fn get_en_name(names: &Option<Names>) -> &str {
.unwrap_or_default()
}
pub fn get_localized_name<'a>(names: &'a Option<Names>, mmdb_key: &str) -> &'a str {
names
.as_ref()
.and_then(|n| match mmdb_key {
"de" => n.german.as_deref().or(n.english.as_deref()),
"es" => n.spanish.as_deref().or(n.english.as_deref()),
"fr" => n.french.as_deref().or(n.english.as_deref()),
"ja" => n.japanese.as_deref().or(n.english.as_deref()),
"pt-BR" => n.brazilian_portuguese.as_deref().or(n.english.as_deref()),
"ru" => n.russian.as_deref().or(n.english.as_deref()),
"zh-CN" => n.simplified_chinese.as_deref().or(n.english.as_deref()),
_ => n.english.as_deref(),
})
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
+7 -2
View File
@@ -1,14 +1,18 @@
use axum::Router;
use axum::middleware;
use axum::routing::get;
use axum::Router;
use crate::handlers::{self, AppState};
use crate::middleware::security_headers;
use crate::static_assets;
pub fn build_router(state: AppState) -> Router {
Router::new()
let localized = Router::new()
.route("/", get(handlers::root))
.with_state(state.clone());
Router::new()
.route("/", get(handlers::root_redirect))
.route("/health", get(handlers::health))
.route("/openapi.json", get(handlers::openapi_json))
.route("/robots.txt", get(handlers::robots_txt))
@@ -24,6 +28,7 @@ pub fn build_router(state: AppState) -> Router {
.route("/hosting", get(handlers::hosting_handler))
.route("/tor", get(handlers::tor_handler))
.route("/static/{*path}", get(static_assets::serve_static))
.nest("/{lang}", localized)
.fallback(handlers::not_found)
.layer(middleware::from_fn(security_headers))
.with_state(state)
+29
View File
@@ -370,6 +370,35 @@ main {
gap: 4px 16px;
}
}
.footer-lang {
margin-block-start: 16px;
}
.lang-select {
appearance: none;
padding: 6px 28px 6px 10px;
border: 1px solid var(--color-separator);
border-radius: 6px;
font-family: inherit;
font-size: 13px;
color: var(--color-label-secondary);
background-color: var(--color-bg);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%238E8E93'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
cursor: pointer;
&:focus-visible {
outline: 2px solid var(--color-blue);
outline-offset: -1px;
}
}
}
[dir="rtl"] .lang-select {
background-position: left 10px center;
padding: 6px 10px 6px 28px;
}
@media (max-width: 480px) {
+12 -4
View File
@@ -1,6 +1,14 @@
<p class="footer-license">Website source code licensed under <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0</a></p>
<nav class="footer-links" aria-label="Project links">
<a href="https://github.com/NetworkCats/rustyip"><i class="icon-social-github" aria-hidden="true"></i> Website Source</a>
<p class="footer-license">{{ t_footer_license }} <a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL-3.0</a></p>
<nav class="footer-links" aria-label="{{ t_nav_aria }}">
<a href="https://github.com/NetworkCats/rustyip"><i class="icon-social-github" aria-hidden="true"></i> {{ t_footer_source }}</a>
<span class="footer-sep" aria-hidden="true">|</span>
<a href="https://github.com/NetworkCats/Merged-IP-Data"><i class="icon-social-github" aria-hidden="true"></i> IP Database</a>
<a href="https://github.com/NetworkCats/Merged-IP-Data"><i class="icon-social-github" aria-hidden="true"></i> {{ t_footer_db }}</a>
</nav>
<div class="footer-lang">
<label for="lang-select" class="sr-only">{{ t_language }}</label>
<select id="lang-select" class="lang-select" autocomplete="off" onchange="if(this.value)window.location.href=this.value">
{% for l in all_locales %}
<option value="/{{ l.tag() }}/"{% if *l == locale %} selected{% endif %}>{{ l.name() }}</option>
{% endfor %}
</select>
</div>
+3 -3
View File
@@ -1,12 +1,12 @@
<form method="GET" action="/" class="search-form" role="search">
<form method="GET" action="/{{ lang_tag }}/" class="search-form" role="search">
<div class="search-input-wrap">
<label for="ip-search" class="sr-only">Search IP address</label>
<label for="ip-search" class="sr-only">{{ t_search_label }}</label>
<i class="icon-magnifier search-icon" aria-hidden="true"></i>
<input
id="ip-search"
type="text"
name="ip"
placeholder="Type to search IP data"
placeholder="{{ t_search_placeholder }}"
value="{{ query }}"
autocomplete="off"
autocapitalize="none"
+1 -1
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="{{ lang_tag }}" dir="{{ html_dir }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+1 -1
View File
@@ -1,6 +1,6 @@
{% extends "base.html" %}
{% block title %}Error - {{ site_domain }}{% endblock %}
{% block title %}{{ t_error }} - {{ site_domain }}{% endblock %}
{% block head_meta %}
<meta name="robots" content="noindex, nofollow">
+1 -1
View File
@@ -11,6 +11,6 @@
<h1 class="error-code">{{ status_code }}</h1>
<p class="error-status">{{ status_text }}</p>
<p class="error-detail">{{ error_message }}</p>
<a href="/" class="error-home-link">Go to Home</a>
<a href="/{{ locale.tag() }}/" class="error-home-link">{{ t_go_home }}</a>
</div>
{% endblock %}
+56 -52
View File
@@ -1,19 +1,23 @@
{% extends "base.html" %}
{% block title %}What is my IP address? - {{ site_domain }}{% endblock %}
{% block title %}{{ t_title }} - {{ site_domain }}{% endblock %}
{% block head_meta %}
<meta name="description" content="Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address.">
<link rel="canonical" href="https://{{ site_domain }}/">
<meta name="description" content="{{ t_description }}">
<link rel="canonical" href="https://{{ site_domain }}/{{ lang_tag }}/">
<meta name="theme-color" content="#007AFF" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#0A84FF" media="(prefers-color-scheme: dark)">
<meta property="og:title" content="What is my IP address? - {{ site_domain }}">
<meta property="og:description" content="Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address.">
<meta property="og:title" content="{{ t_title }} - {{ site_domain }}">
<meta property="og:description" content="{{ t_description }}">
<meta property="og:type" content="website">
<meta property="og:url" content="https://{{ site_domain }}/">
<meta property="og:url" content="https://{{ site_domain }}/{{ lang_tag }}/">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="What is my IP address? - {{ site_domain }}">
<meta name="twitter:description" content="Find your public IP address instantly. Look up geolocation, ASN, organization, and proxy/VPN/Tor detection for any IP address.">
<meta name="twitter:title" content="{{ t_title }} - {{ site_domain }}">
<meta name="twitter:description" content="{{ t_description }}">
{% for l in all_locales %}
<link rel="alternate" hreflang="{{ l.tag() }}" href="https://{{ site_domain }}/{{ l.tag() }}/">
{% endfor %}
<link rel="alternate" hreflang="x-default" href="https://{{ site_domain }}/">
{% endblock %}
{% block head_preconnect %}
@@ -30,61 +34,61 @@
<h1 class="ip-display">{{ ip }}</h1>
<table class="info-table" aria-label="IP address information">
<table class="info-table" aria-label="{{ t_table_aria }}">
<tbody>
{% if !asn.is_empty() %}
<tr>
<th scope="row">ASN</th>
<th scope="row">{{ t_asn }}</th>
<td>{{ asn }}</td>
</tr>
{% endif %}
{% if !org.is_empty() %}
<tr>
<th scope="row">Org</th>
<th scope="row">{{ t_org }}</th>
<td>{{ org }}</td>
</tr>
{% endif %}
{% if !country.is_empty() %}
<tr>
<th scope="row">Country</th>
<th scope="row">{{ t_country }}</th>
<td>{{ country }}</td>
</tr>
{% endif %}
{% if !city.is_empty() %}
<tr>
<th scope="row">City</th>
<th scope="row">{{ t_city }}</th>
<td>{{ city }}</td>
</tr>
{% endif %}
<tr>
<th scope="row">Proxy</th>
<td>{% if is_proxy %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">Yes</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">No</span>{% endif %}</td>
<th scope="row">{{ t_proxy }}</th>
<td>{% if is_proxy %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">{{ t_yes }}</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">{{ t_no }}</span>{% endif %}</td>
</tr>
<tr>
<th scope="row">VPN</th>
<td>{% if is_vpn %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">Yes</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">No</span>{% endif %}</td>
<th scope="row">{{ t_vpn }}</th>
<td>{% if is_vpn %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">{{ t_yes }}</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">{{ t_no }}</span>{% endif %}</td>
</tr>
<tr>
<th scope="row">Hosting</th>
<td>{% if is_hosting %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">Yes</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">No</span>{% endif %}</td>
<th scope="row">{{ t_hosting }}</th>
<td>{% if is_hosting %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">{{ t_yes }}</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">{{ t_no }}</span>{% endif %}</td>
</tr>
<tr>
<th scope="row">Tor</th>
<td>{% if is_tor %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">Yes</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">No</span>{% endif %}</td>
<th scope="row">{{ t_tor }}</th>
<td>{% if is_tor %}<i class="icon-check bool-true" aria-hidden="true"></i><span class="sr-only">{{ t_yes }}</span>{% else %}<i class="icon-minus bool-false" aria-hidden="true"></i><span class="sr-only">{{ t_no }}</span>{% endif %}</td>
</tr>
</tbody>
</table>
<section class="faq" aria-label="Frequently Asked Questions">
<h2 class="faq-title">FAQ</h2>
<section class="faq" aria-label="{{ t_faq_aria }}">
<h2 class="faq-title">{{ t_faq }}</h2>
<details open>
<summary>How to use this service from the command line</summary>
<summary>{{ t_faq_cli_q }}</summary>
<div class="faq-content">
<p>You can use various command-line tools such as curl, httpie, and wget to use this service. Taking the most popular one, curl, as an example:</p>
<p>Query current IP:</p>
<p>{{ t_faq_cli_a }}</p>
<p>{{ t_faq_cli_query_current }}</p>
<pre><code>curl {{ site_domain }}</code></pre>
<p>Query information for your own IP:</p>
<p>{{ t_faq_cli_query_own }}</p>
<pre><code>curl {{ site_domain }}/asn
curl {{ site_domain }}/org
@@ -100,7 +104,7 @@ curl {{ site_domain }}/vpn
curl {{ site_domain }}/hosting
curl {{ site_domain }}/tor</code></pre>
<p>Or display all information in JSON format:</p>
<p>{{ t_faq_cli_json }}</p>
<pre><code>curl {{ site_domain }}/json
{
"ip": "1.1.1.1",
@@ -111,7 +115,7 @@ curl {{ site_domain }}/tor</code></pre>
"asn": { ... },
"proxy": { ... }
}</code></pre>
<p>Query information for a specific IP:</p>
<p>{{ t_faq_cli_specific }}</p>
<pre><code>curl {{ site_domain }}/json?ip=1.1.1.1
{
"ip": "1.1.1.1",
@@ -126,32 +130,32 @@ curl {{ site_domain }}/tor</code></pre>
</details>
<details open>
<summary>How to force the display of my IPv4 or IPv6 address?</summary>
<summary>{{ t_faq_ipv46_q }}</summary>
<div class="faq-content">
<p>You can use <code>curl -4</code> or <code>curl -6</code> to view only your IPv4 or IPv6 address respectively.</p>
<p>{{ t_faq_ipv46_a|safe }}</p>
</div>
</details>
<details open>
<summary>How to get a JSON format response?</summary>
<summary>{{ t_faq_json_q }}</summary>
<div class="faq-content">
<p>You can send a request to the <code>/json</code> endpoint, or send a request with an <code>application/json</code> header.</p>
<p>{{ t_faq_json_a|safe }}</p>
</div>
</details>
<details open>
<summary>Can I automate calls to your service via programs?</summary>
<summary>{{ t_faq_automate_q }}</summary>
<div class="faq-content">
<p>Certainly, as long as the rate limit is respected. Under normal conditions, the rate limit is 30 RPM (Requests Per Minute); under high load, we will increase the restriction to 10-5 RPM.</p>
<p>Our API is only suitable for manual calls or small-scale projects. If your website uses our API to query visitor IPs, please ensure you use a message queue to send requests to avoid blocking. If your project has high traffic or is latency-sensitive, please use our open-source offline database: <a href="https://github.com/NetworkCats/Merged-IP-Data">Merged IP Database</a>, which is the same database used by this project.</p>
<p>{{ t_faq_automate_a1 }}</p>
<p>{{ t_faq_automate_a2|safe }}</p>
</div>
</details>
<details open>
<summary>Where does the IP data come from?</summary>
<summary>{{ t_faq_data_q }}</summary>
<div class="faq-content">
<p>IP geographic data primarily comes from the free databases of MaxMind and DB-IP; AS data comes from IPinfo's free database; and IP proxy data comes from my own <a href="https://github.com/NetworkCats/OpenProxyDB">OpenProxyDB</a> database.</p>
<p>Data Attributions:</p>
<p>{{ t_faq_data_a|safe }}</p>
<p>{{ t_faq_data_attr }}</p>
<ul>
<li>MaxMind GeoLite2: <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a></li>
<li>IPinfo Lite: <a href="https://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a></li>
@@ -163,9 +167,9 @@ curl {{ site_domain }}/tor</code></pre>
</details>
<details open>
<summary>Can I run my own service?</summary>
<summary>{{ t_faq_selfhost_q }}</summary>
<div class="faq-content">
<p>Yes, the source code and database for this project are both open-source and hosted on GitHub:</p>
<p>{{ t_faq_selfhost_a }}</p>
<ul>
<li><a href="https://github.com/NetworkCats/rustyip">https://github.com/NetworkCats/rustyip</a></li>
<li><a href="https://github.com/NetworkCats/Merged-IP-Data">https://github.com/NetworkCats/Merged-IP-Data</a></li>
@@ -174,27 +178,27 @@ curl {{ site_domain }}/tor</code></pre>
</details>
<details open>
<summary>How is this website different from other IP lookup sites?</summary>
<summary>{{ t_faq_diff_q }}</summary>
<div class="faq-content">
<p>The biggest difference is that our database includes proxy-related information, and the proxy intelligence we provide is highly accurate. We have privately compared it with commercial proxy detection databases like IPinfo; our database's precision is fully comparable to commercial ones, and it is even more accurate in detecting residential proxies.</p>
<p>Secondly, our geographic coverage for IPv6 is more comprehensive because we merge multiple databases to provide more complete data.</p>
<p>Our UX is also better than any IP lookup site on the market. The site runs without any JavaScript, and we only load a minimal amount of external resources (fonts, icons), making the initial load speed extremely fast.</p>
<p>{{ t_faq_diff_a1 }}</p>
<p>{{ t_faq_diff_a2 }}</p>
<p>{{ t_faq_diff_a3 }}</p>
</div>
</details>
<details open>
<summary>Is this service stable?</summary>
<summary>{{ t_faq_stable_q }}</summary>
<div class="faq-content">
<p>The business logic of this service is written entirely in Rust, offering excellent performance that ensures the site remains operational even under L7 DDoS attacks. The site runs on a VPS with 2 cores and 2 GB of RAM, which is already overkill for this setup -- unless we encounter a massive L7 DDoS attack, though we have Cloudflare in front of us.</p>
<p>Running this website only costs me $18.00 per month. Because the cost is so low, I will not stop operations due to hardware costs in the foreseeable future, nor will I add any advertisements.</p>
<p>Rest assured, this service will definitely not "Rug Pull."</p>
<p>{{ t_faq_stable_a1 }}</p>
<p>{{ t_faq_stable_a2 }}</p>
<p>{{ t_faq_stable_a3 }}</p>
</div>
</details>
<details open>
<summary>How can I sponsor this service?</summary>
<summary>{{ t_faq_sponsor_q }}</summary>
<div class="faq-content">
<p>I accept cryptocurrency donations. If you would like to sponsor, please contact: <a href="mailto:networkcatlol@gmail.com">networkcatlol@gmail.com</a></p>
<p>{{ t_faq_sponsor_a }} <a href="mailto:networkcatlol@gmail.com">networkcatlol@gmail.com</a></p>
</div>
</details>
</section>
@@ -205,7 +209,7 @@ curl {{ site_domain }}/tor</code></pre>
<div class="footer-inner">
{% include "_footer.html" %}
<div class="footer-attribution">
<p>IP data provided by:</p>
<p>{{ t_footer_attribution }}</p>
<ul>
<li><a href="https://www.maxmind.com/">MaxMind GeoLite2</a></li>
<li><a href="https://ipinfo.io/">IPinfo Lite</a></li>
+265 -21
View File
@@ -56,6 +56,19 @@ async fn get_with_headers(
(status, text)
}
async fn get_response(
app: &axum::Router,
uri: &str,
headers: Vec<(&str, &str)>,
) -> axum::http::Response<axum::body::Body> {
let mut builder = Request::builder().uri(uri).method("GET");
for (k, v) in headers {
builder = builder.header(k, v);
}
let request = builder.body(axum::body::Body::empty()).unwrap();
app.clone().oneshot(request).await.unwrap()
}
#[tokio::test]
async fn health_returns_200() {
let app = build_test_app();
@@ -206,12 +219,64 @@ async fn accept_json_returns_json() {
assert_eq!(json["ip"], "45.77.77.77");
}
// The root path for browser requests now redirects to /{lang}/
#[tokio::test]
async fn browser_returns_html() {
async fn root_redirects_browser_to_lang_path() {
let app = build_test_app();
let response = get_response(
&app,
"/",
vec![
(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
),
("CF-Connecting-IP", "45.77.77.77"),
],
)
.await;
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
let location = response
.headers()
.get("location")
.unwrap()
.to_str()
.unwrap();
assert!(
location.starts_with("/en"),
"redirect should go to /en, got: {location}"
);
}
#[tokio::test]
async fn root_redirects_to_japanese_for_ja_accept_language() {
let app = build_test_app();
let response = get_response(
&app,
"/",
vec![
("User-Agent", "Mozilla/5.0"),
("Accept-Language", "ja,en;q=0.5"),
("CF-Connecting-IP", "1.1.1.1"),
],
)
.await;
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
let location = response
.headers()
.get("location")
.unwrap()
.to_str()
.unwrap();
assert_eq!(location, "/ja");
}
#[tokio::test]
async fn browser_returns_html_at_lang_path() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=45.77.77.77",
"/en?ip=45.77.77.77",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -228,11 +293,55 @@ async fn browser_returns_html() {
}
#[tokio::test]
async fn root_without_cf_header_returns_error() {
async fn browser_returns_html_in_spanish() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/es?ip=45.77.77.77",
vec![("User-Agent", "Mozilla/5.0")],
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(body.contains("lang=\"es\""));
assert!(body.contains("45.77.77.77"));
}
#[tokio::test]
async fn browser_returns_html_in_arabic_with_rtl() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/ar?ip=45.77.77.77",
vec![("User-Agent", "Mozilla/5.0")],
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(body.contains("lang=\"ar\""));
assert!(body.contains("dir=\"rtl\""));
}
#[tokio::test]
async fn root_without_cf_header_redirects() {
let app = build_test_app();
let response = get_response(
&app,
"/",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
)],
)
.await;
// Root should redirect to /en when no Accept-Language header
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
}
#[tokio::test]
async fn lang_path_without_cf_header_returns_error() {
let app = build_test_app();
let (status, _) = get_with_headers(
&app,
"/",
"/en",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -310,7 +419,6 @@ async fn whitespace_ip_query_falls_back_to_cf_header() {
#[tokio::test]
async fn ip_not_in_database_returns_500() {
let app = build_test_app();
// 0.0.0.0 is unlikely to be in a geo database
let (status, _) = get(&app, "/json?ip=0.0.0.0").await;
assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
}
@@ -320,7 +428,7 @@ async fn cli_with_query_param_returns_html() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=45.77.77.77",
"/en?ip=45.77.77.77",
vec![
("User-Agent", "curl/8.7.1"),
("CF-Connecting-IP", "1.2.3.4"),
@@ -328,7 +436,6 @@ async fn cli_with_query_param_returns_html() {
)
.await;
assert_eq!(status, StatusCode::OK);
// When CLI sends ?ip=, the shortcut is bypassed and HTML is returned
assert!(body.contains("<!DOCTYPE html>"));
assert!(body.contains("45.77.77.77"));
}
@@ -338,7 +445,7 @@ async fn accept_json_with_query_param_returns_html() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=45.77.77.77",
"/en?ip=45.77.77.77",
vec![
("Accept", "application/json"),
("CF-Connecting-IP", "8.8.8.8"),
@@ -346,7 +453,6 @@ async fn accept_json_with_query_param_returns_html() {
)
.await;
assert_eq!(status, StatusCode::OK);
// When Accept: application/json is sent with ?ip=, the JSON shortcut is bypassed
assert!(body.contains("<!DOCTYPE html>"));
assert!(body.contains("45.77.77.77"));
}
@@ -440,7 +546,13 @@ async fn sitemap_xml_returns_valid_response() {
let (status, body) = get(&app, "/sitemap.xml").await;
assert_eq!(status, StatusCode::OK);
assert!(body.contains("<?xml version=\"1.0\""));
assert!(body.contains("<loc>https://test.example.com/</loc>"));
assert!(body.contains("<loc>https://test.example.com/en/</loc>"));
assert!(body.contains("<loc>https://test.example.com/es/</loc>"));
assert!(body.contains("<loc>https://test.example.com/ja/</loc>"));
assert!(body.contains("hreflang=\"en\""));
assert!(body.contains("hreflang=\"x-default\""));
assert!(body.contains("hreflang=\"zh-Hant\""));
assert!(body.contains("xmlns:xhtml"));
}
#[tokio::test]
@@ -448,7 +560,7 @@ async fn html_contains_seo_meta_tags() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=45.77.77.77",
"/en?ip=45.77.77.77",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -463,6 +575,9 @@ async fn html_contains_seo_meta_tags() {
assert!(body.contains("<meta property=\"og:type\" content=\"website\""));
assert!(body.contains("<meta name=\"twitter:card\" content=\"summary\""));
assert!(body.contains("<meta name=\"theme-color\""));
assert!(body.contains("hreflang=\"en\""));
assert!(body.contains("hreflang=\"x-default\""));
assert!(body.contains("hreflang=\"es\""));
}
#[tokio::test]
@@ -470,7 +585,7 @@ async fn html_uses_th_for_row_headers() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=45.77.77.77",
"/en?ip=45.77.77.77",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -483,14 +598,86 @@ async fn html_uses_th_for_row_headers() {
assert!(body.contains("autocapitalize=\"none\""));
}
#[tokio::test]
async fn html_contains_lang_attribute() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/de?ip=45.77.77.77",
vec![("User-Agent", "Mozilla/5.0")],
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(body.contains("lang=\"de\""));
}
#[tokio::test]
async fn html_contains_language_selector() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/en?ip=45.77.77.77",
vec![("User-Agent", "Mozilla/5.0")],
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(body.contains("lang-select"));
assert!(body.contains("value=\"/en/\""));
assert!(body.contains("value=\"/es/\""));
assert!(body.contains("value=\"/ja/\""));
assert!(body.contains("value=\"/zh-Hant/\""));
assert!(body.contains("value=\"/ar/\""));
}
#[tokio::test]
async fn search_form_posts_to_lang_path() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/fr?ip=45.77.77.77",
vec![("User-Agent", "Mozilla/5.0")],
)
.await;
assert_eq!(status, StatusCode::OK);
assert!(body.contains("action=\"/fr/\""));
}
#[tokio::test]
async fn invalid_lang_tag_returns_404() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/xx?ip=45.77.77.77",
vec![("User-Agent", "Mozilla/5.0")],
)
.await;
assert_eq!(status, StatusCode::NOT_FOUND);
assert!(body.contains("404"));
}
// --- Dev mode tests ---
#[tokio::test]
async fn dev_mode_root_returns_html_without_cf_header() {
async fn dev_mode_root_redirects_without_cf_header() {
let app = build_test_app_with_dev_mode(true);
let response = get_response(
&app,
"/",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
)],
)
.await;
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
}
#[tokio::test]
async fn dev_mode_lang_path_returns_html_without_cf_header() {
let app = build_test_app_with_dev_mode(true);
let (status, body) = get_with_headers(
&app,
"/",
"/en",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -579,7 +766,7 @@ async fn browser_invalid_ip_shows_alert_html() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=not-an-ip",
"/en?ip=not-an-ip",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -589,7 +776,6 @@ async fn browser_invalid_ip_shows_alert_html() {
assert_eq!(status, StatusCode::BAD_REQUEST);
assert!(body.contains("<!DOCTYPE html>"));
assert!(body.contains("alert("));
assert!(body.contains("Invalid IP address"));
assert!(body.contains("not-an-ip"));
}
@@ -598,7 +784,7 @@ async fn browser_db_lookup_failed_shows_alert_html() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=0.0.0.0",
"/en?ip=0.0.0.0",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -608,7 +794,6 @@ async fn browser_db_lookup_failed_shows_alert_html() {
assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
assert!(body.contains("<!DOCTYPE html>"));
assert!(body.contains("alert("));
assert!(body.contains("Database lookup failed"));
}
#[tokio::test]
@@ -616,7 +801,7 @@ async fn browser_missing_client_ip_shows_alert_html() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/",
"/en",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -626,7 +811,6 @@ async fn browser_missing_client_ip_shows_alert_html() {
assert_eq!(status, StatusCode::BAD_REQUEST);
assert!(body.contains("<!DOCTYPE html>"));
assert!(body.contains("alert("));
assert!(body.contains("Missing client IP address"));
}
#[tokio::test]
@@ -643,7 +827,7 @@ async fn error_alert_page_has_search_form() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/?ip=bad-ip",
"/en?ip=bad-ip",
vec![(
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
@@ -655,3 +839,63 @@ async fn error_alert_page_has_search_form() {
assert!(body.contains("value=\"bad-ip\""));
assert!(body.contains("autofocus"));
}
// --- i18n-specific tests ---
#[tokio::test]
async fn root_with_ip_query_redirects_with_ip() {
let app = build_test_app();
let response = get_response(
&app,
"/?ip=1.2.3.4",
vec![
("User-Agent", "Mozilla/5.0"),
("CF-Connecting-IP", "1.1.1.1"),
],
)
.await;
assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
let location = response
.headers()
.get("location")
.unwrap()
.to_str()
.unwrap();
assert!(
location.contains("ip=1.2.3.4"),
"redirect should contain ip param: {location}"
);
}
#[tokio::test]
async fn all_locale_paths_return_200() {
let app = build_test_app_with_dev_mode(true);
let tags = [
"en", "es", "de", "ja", "ko", "id", "fr", "ru", "pt", "it", "zh-Hant", "zh-Hans", "nl",
"ar",
];
for tag in tags {
let uri = format!("/{tag}");
let (status, body) =
get_with_headers(&app, &uri, vec![("User-Agent", "Mozilla/5.0")]).await;
assert_eq!(status, StatusCode::OK, "expected 200 for /{tag}");
assert!(
body.contains(&format!("lang=\"{tag}\"")),
"/{tag} should have lang=\"{tag}\" attribute"
);
}
}
#[tokio::test]
async fn error_page_has_localized_go_home_link() {
let app = build_test_app();
let (status, body) = get_with_headers(
&app,
"/nonexistent",
vec![("User-Agent", "Mozilla/5.0"), ("Accept-Language", "en")],
)
.await;
assert_eq!(status, StatusCode::NOT_FOUND);
assert!(body.contains("Go to Home"));
assert!(body.contains("href=\"/en/\""));
}