diff --git a/Cargo.lock b/Cargo.lock index 185fbd6..d05631b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 975d24e..350f5f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/locales/ar.po b/locales/ar.po new file mode 100644 index 0000000..2fac994 --- /dev/null +++ b/locales/ar.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "يمكنك استخدام curl -4 أو curl -6 لعرض عنوان IPv4 أو IPv6 الخاص بك فقط على التوالي." + +msgid "How to get a JSON format response?" +msgstr "كيف أحصل على استجابة بصيغة JSON؟" + +msgid "You can send a request to the /json endpoint, or send a request with an application/json header." +msgstr "يمكنك إرسال طلب إلى نقطة النهاية /json، أو إرسال طلب مع ترويسة application/json." + +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: Merged IP Database, which is the same database used by this project." +msgstr "واجهة برمجة التطبيقات الخاصة بنا مناسبة فقط للاستدعاءات اليدوية أو المشاريع الصغيرة. إذا كان موقعك الإلكتروني يستخدم واجهة برمجة التطبيقات الخاصة بنا للاستعلام عن عناوين IP للزوار، يرجى التأكد من استخدام قائمة انتظار الرسائل لإرسال الطلبات لتجنب الحجب. إذا كان مشروعك يتعامل مع حركة مرور عالية أو حساس لزمن الاستجابة، يرجى استخدام قاعدة البيانات المحلية مفتوحة المصدر: Merged IP Database، وهي نفس قاعدة البيانات المستخدمة في هذا المشروع." + +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 OpenProxyDB database." +msgstr "تأتي البيانات الجغرافية لعناوين IP بشكل أساسي من قواعد البيانات المجانية لـ MaxMind وDB-IP؛ وتأتي بيانات AS من قاعدة بيانات IPinfo المجانية؛ وتأتي بيانات بروكسي IP من قاعدة بياناتي الخاصة OpenProxyDB." + +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 "فشل عرض القالب" diff --git a/locales/de.po b/locales/de.po new file mode 100644 index 0000000..6560706 --- /dev/null +++ b/locales/de.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "Sie können curl -4 oder curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "Sie können eine Anfrage an den /json-Endpunkt senden oder eine Anfrage mit einem application/json-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: Merged IP Database, 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: Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB-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" diff --git a/locales/en.po b/locales/en.po new file mode 100644 index 0000000..56a6a4c --- /dev/null +++ b/locales/en.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "You can use curl -4 or curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "You can send a request to the /json endpoint, or send a request with an application/json 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: Merged IP Database, 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: Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB 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" diff --git a/locales/es.po b/locales/es.po new file mode 100644 index 0000000..7dc2819 --- /dev/null +++ b/locales/es.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "Puedes usar curl -4 o curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "Puedes enviar una solicitud al endpoint /json, o enviar una solicitud con el encabezado application/json." + +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: Merged IP Database, 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: Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB." + +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" diff --git a/locales/fr.po b/locales/fr.po new file mode 100644 index 0000000..40ebdbb --- /dev/null +++ b/locales/fr.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "Vous pouvez utiliser curl -4 ou curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "Vous pouvez envoyer une requete au point de terminaison /json, ou envoyer une requete avec un en-tete application/json." + +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: Merged IP Database, 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 : Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB." + +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" diff --git a/locales/id.po b/locales/id.po new file mode 100644 index 0000000..78a4ed4 --- /dev/null +++ b/locales/id.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "Anda dapat menggunakan curl -4 atau curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "Anda dapat mengirim permintaan ke endpoint /json, atau mengirim permintaan dengan header application/json." + +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: Merged IP Database, 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: Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB 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" diff --git a/locales/it.po b/locales/it.po new file mode 100644 index 0000000..e3f3cc5 --- /dev/null +++ b/locales/it.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "Puoi usare curl -4 o curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "Puoi inviare una richiesta all'endpoint /json, oppure inviare una richiesta con un'intestazione application/json." + +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: Merged IP Database, 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: Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB." + +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" diff --git a/locales/ja.po b/locales/ja.po new file mode 100644 index 0000000..add5ac1 --- /dev/null +++ b/locales/ja.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "curl -4 または curl -6 を使用することで、それぞれIPv4またはIPv6アドレスのみを表示できます。" + +msgid "How to get a JSON format response?" +msgstr "JSON形式のレスポンスを取得するには?" + +msgid "You can send a request to the /json endpoint, or send a request with an application/json header." +msgstr "/json エンドポイントにリクエストを送信するか、application/json ヘッダーを付けてリクエストを送信してください。" + +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: Merged IP Database, which is the same database used by this project." +msgstr "当APIは手動での呼び出しや小規模プロジェクトにのみ適しています。ウェブサイトで当APIを使用して訪問者のIPを照会する場合は、ブロッキングを避けるためにメッセージキューを使用してリクエストを送信してください。高トラフィックまたはレイテンシに敏感なプロジェクトの場合は、当プロジェクトと同じデータベースを使用しているオープンソースのオフラインデータベース:Merged IP Database をご利用ください。" + +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 OpenProxyDB database." +msgstr "IP地理データは主にMaxMindとDB-IPの無料データベースから取得しています。ASデータはIPinfoの無料データベースから取得し、IPプロキシデータは独自の OpenProxyDB データベースから取得しています。" + +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 "テンプレートのレンダリングに失敗しました" diff --git a/locales/ko.po b/locales/ko.po new file mode 100644 index 0000000..8f6c8b8 --- /dev/null +++ b/locales/ko.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "curl -4 또는 curl -6을 사용하여 각각 IPv4 또는 IPv6 주소만 확인할 수 있습니다." + +msgid "How to get a JSON format response?" +msgstr "JSON 형식의 응답을 받으려면 어떻게 하나요?" + +msgid "You can send a request to the /json endpoint, or send a request with an application/json header." +msgstr "/json 엔드포인트로 요청을 보내거나, application/json 헤더를 포함하여 요청을 보낼 수 있습니다." + +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: Merged IP Database, which is the same database used by this project." +msgstr "저희 API는 수동 호출이나 소규모 프로젝트에만 적합합니다. 웹사이트에서 저희 API를 사용하여 방문자 IP를 조회하는 경우, 차단을 방지하기 위해 메시지 큐를 사용하여 요청을 보내주세요. 트래픽이 많거나 지연 시간에 민감한 프로젝트의 경우, 이 프로젝트에서 사용하는 것과 동일한 오픈소스 오프라인 데이터베이스를 사용해 주세요: Merged IP Database." + +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 OpenProxyDB database." +msgstr "IP 지리 데이터는 주로 MaxMind와 DB-IP의 무료 데이터베이스에서 제공됩니다. AS 데이터는 IPinfo의 무료 데이터베이스에서, IP 프록시 데이터는 자체 OpenProxyDB 데이터베이스에서 제공됩니다." + +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 "템플릿 렌더링 실패" diff --git a/locales/messages.pot b/locales/messages.pot new file mode 100644 index 0000000..d20408d --- /dev/null +++ b/locales/messages.pot @@ -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 curl -4 or curl -6 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 /json endpoint, or send a request with an application/json 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: Merged IP Database, 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 OpenProxyDB 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 "" diff --git a/locales/nl.po b/locales/nl.po new file mode 100644 index 0000000..61459b3 --- /dev/null +++ b/locales/nl.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "U kunt curl -4 of curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "U kunt een verzoek sturen naar het /json-eindpunt, of een verzoek sturen met een application/json-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: Merged IP Database, 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: Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB-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" diff --git a/locales/pt.po b/locales/pt.po new file mode 100644 index 0000000..120fd62 --- /dev/null +++ b/locales/pt.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "Você pode usar curl -4 ou curl -6 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 /json endpoint, or send a request with an application/json header." +msgstr "Você pode enviar uma requisição para o endpoint /json, ou enviar uma requisição com o cabeçalho application/json." + +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: Merged IP Database, 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: Merged IP Database, 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 OpenProxyDB 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 OpenProxyDB." + +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" diff --git a/locales/ru.po b/locales/ru.po new file mode 100644 index 0000000..ca53455 --- /dev/null +++ b/locales/ru.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "Вы можете использовать curl -4 или curl -6 для просмотра только вашего IPv4 или IPv6 адреса соответственно." + +msgid "How to get a JSON format response?" +msgstr "Как получить ответ в формате JSON?" + +msgid "You can send a request to the /json endpoint, or send a request with an application/json header." +msgstr "Вы можете отправить запрос на эндпоинт /json или отправить запрос с заголовком application/json." + +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: Merged IP Database, which is the same database used by this project." +msgstr "Наш API подходит только для ручных вызовов или небольших проектов. Если ваш сайт использует наш API для запроса IP-адресов посетителей, убедитесь, что вы используете очередь сообщений для отправки запросов во избежание блокировки. Если ваш проект имеет высокий трафик или чувствителен к задержкам, используйте нашу офлайн-базу данных с открытым исходным кодом: Merged IP Database, которая является той же базой данных, что используется в этом проекте." + +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 OpenProxyDB database." +msgstr "Географические данные IP в основном поступают из бесплатных баз данных MaxMind и DB-IP; данные AS -- из бесплатной базы данных IPinfo; а данные о прокси IP -- из моей собственной базы данных OpenProxyDB." + +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 "Ошибка рендеринга шаблона" diff --git a/locales/zh-Hans.po b/locales/zh-Hans.po new file mode 100644 index 0000000..d4452e8 --- /dev/null +++ b/locales/zh-Hans.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "您可以使用 curl -4 或 curl -6 来分别仅查看您的 IPv4 或 IPv6 地址。" + +msgid "How to get a JSON format response?" +msgstr "如何获取 JSON 格式的响应?" + +msgid "You can send a request to the /json endpoint, or send a request with an application/json header." +msgstr "您可以向 /json 端点发送请求,或发送带有 application/json 头的请求。" + +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: Merged IP Database, which is the same database used by this project." +msgstr "我们的 API 仅适用于手动调用或小规模项目。如果您的网站使用我们的 API 查询访客 IP,请确保使用消息队列发送请求以避免阻塞。如果您的项目流量较大或对延迟敏感,请使用我们的开源离线数据库:Merged IP Database,这与本项目使用的是同一个数据库。" + +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 OpenProxyDB database." +msgstr "IP 地理数据主要来自 MaxMind 和 DB-IP 的免费数据库;AS 数据来自 IPinfo 的免费数据库;IP 代理数据来自我自己的 OpenProxyDB 数据库。" + +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 "模板渲染失败" diff --git a/locales/zh-Hant.po b/locales/zh-Hant.po new file mode 100644 index 0000000..4c76256 --- /dev/null +++ b/locales/zh-Hant.po @@ -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 curl -4 or curl -6 to view only your IPv4 or IPv6 address respectively." +msgstr "您可以使用 curl -4 或 curl -6 來分別僅查看您的 IPv4 或 IPv6 位址。" + +msgid "How to get a JSON format response?" +msgstr "如何取得 JSON 格式的回應?" + +msgid "You can send a request to the /json endpoint, or send a request with an application/json header." +msgstr "您可以向 /json 端點發送請求,或發送帶有 application/json 標頭的請求。" + +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: Merged IP Database, which is the same database used by this project." +msgstr "我們的 API 僅適用於手動呼叫或小型專案。如果您的網站使用我們的 API 查詢訪客 IP,請確保使用訊息佇列來發送請求以避免阻塞。如果您的專案流量較高或對延遲敏感,請使用我們的開源離線資料庫:Merged IP Database,這與本專案使用的資料庫相同。" + +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 OpenProxyDB database." +msgstr "IP 地理資料主要來自 MaxMind 和 DB-IP 的免費資料庫;AS 資料來自 IPinfo 的免費資料庫;IP 代理資料則來自我自己的 OpenProxyDB 資料庫。" + +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 "範本渲染失敗" diff --git a/src/handlers.rs b/src/handlers.rs index 0ee50d6..cab5ce8 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -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 { @@ -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 curl -4 or curl -6 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 /json endpoint, or send a request with an application/json 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: Merged IP Database, 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 OpenProxyDB 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, status: StatusCode, message: &str) -> Response { +fn render_error_page( + site_domain: &Arc, + 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, status: StatusCode, message: &str) } } -pub async fn root( +pub async fn root_redirect( State(state): State, headers: HeaderMap, Query(query): Query, ) -> 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, + Path(lang): Path, + headers: HeaderMap, + Query(query): Query, +) -> 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 { } pub async fn robots_txt(State(state): State) -> 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) -> Response { } pub async fn sitemap_xml(State(state): State) -> Response { + let mut urls = String::new(); + let domain = &state.site_domain; + + for locale in Locale::ALL { + let tag = locale.tag(); + urls.push_str(&format!( + " \n https://{domain}/{tag}/\n" + )); + for alt in Locale::ALL { + let alt_tag = alt.tag(); + urls.push_str(&format!( + " \n" + )); + } + urls.push_str( + &format!(" \n"), + ); + urls.push_str(" \n"); + } + let body = format!( "\n\ - \n\ - \x20 \n\ - \x20 https://{}/\n\ - \x20 \n\ - \n", - state.site_domain + \n\ + {urls}\ + \n" ); ( [(header::CONTENT_TYPE, "application/xml; charset=utf-8")], @@ -389,9 +787,11 @@ pub async fn sitemap_xml(State(state): State) -> Response { .into_response() } -pub async fn not_found(State(state): State) -> Response { +pub async fn not_found(State(state): State, 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.", ) diff --git a/src/i18n.rs b/src/i18n.rs new file mode 100644 index 0000000..ab0a6c7 --- /dev/null +++ b/src/i18n.rs @@ -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 { + 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, +} + +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, ¤t_msgid, ¤t_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, ¤t_msgid, ¤t_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, ¤t_msgid, ¤t_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 = 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 { + 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"); + } +} diff --git a/src/lib.rs b/src/lib.rs index a02a4e8..7e3fbd8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/models.rs b/src/models.rs index a93a0b0..8819f2b 100644 --- a/src/models.rs +++ b/src/models.rs @@ -424,6 +424,22 @@ pub fn get_en_name(names: &Option) -> &str { .unwrap_or_default() } +pub fn get_localized_name<'a>(names: &'a Option, 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::*; diff --git a/src/routes.rs b/src/routes.rs index 4b78ca6..7bf7dba 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -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) diff --git a/static/style.css b/static/style.css index ecb783e..ea30c03 100644 --- a/static/style.css +++ b/static/style.css @@ -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) { diff --git a/templates/_footer.html b/templates/_footer.html index e81b49d..7cff443 100644 --- a/templates/_footer.html +++ b/templates/_footer.html @@ -1,6 +1,14 @@ - -