Skip to content

Commit

Permalink
Multilingual support via i18next
Browse files Browse the repository at this point in the history
  • Loading branch information
txusko committed Aug 25, 2022
1 parent f02ab61 commit db73ddb
Show file tree
Hide file tree
Showing 20 changed files with 485 additions and 248 deletions.
4 changes: 3 additions & 1 deletion .env
@@ -1,3 +1,5 @@
REACT_APP_APP_LANG=tr
REACT_APP_GOOGLE_TM=G-5SRJGZZ5XG
REACT_APP_VERSION=$npm_package_version
GENERATE_SOURCEMAP=false
REACT_APP_PUBLIC_URL=https://heardle-tr.app
REACT_APP_PUBLIC_URL=https://heardle-tr.app
5 changes: 5 additions & 0 deletions package.json
Expand Up @@ -12,8 +12,13 @@
"@types/react-dom": "^17.0.13",
"copy-to-clipboard": "^3.3.1",
"firebase": "^9.6.8",
"i18next": "^21.9.1",
"i18next-browser-languagedetector": "^6.1.5",
"i18next-http-backend": "^1.4.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet-async": "^1.3.0",
"react-i18next": "^11.18.4",
"react-scripts": "5.0.1",
"react-select": "^5.2.2",
"typescript": "^4.6.2",
Expand Down
56 changes: 4 additions & 52 deletions public/index.html
@@ -1,62 +1,14 @@
<!DOCTYPE html>
<html lang="tr">
<html lang="%REACT_APP_APP_LANG%">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png">
<link rel="mask-icon" href="%PUBLIC_URL%/safari-pinned-tab.svg" color="#5bbad5">
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-touch-icon.png">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#121212">

<meta name="description"
content="Heardle Türkçe | Günlük olarak yayınlanan şarkıları tahmin edip sonuçlarını paylasabileceginiz basit bir web oyunudur." />
<meta itemprop="name" content="Heardle Türkçe - Wordle oyununun müzik versiyonu">
<meta itemprop="image" content="%PUBLIC_URL%/logo192.png">

<meta property="og:locale" content="tr_TR" />
<meta property="og:url" content="%REACT_APP_PUBLIC_URL%">
<meta property="og:type" content="website">
<meta property="og:title" content="Heardle Türkçe - Wordle oyununun müzik versiyonu">
<meta property="og:description"
content="Heardle Türkçe - Günlük olarak yayınlanan şarkıları tahmin edip sonuçlarını paylasabileceginiz basit bir web oyunudur.">

<meta property="og:image" content="%REACT_APP_PUBLIC_URL%/og-image-1200-600.jpg?%REACT_APP_VERSION%">
<meta property="og:image:type" content="image/jpg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="600" />
<meta property="og:image:alt" content="Heardle Türkçe - Günün şarkısını dinleyerek tahmin etmeye çalışan insanlar" />

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Heardle Türkçe - Wordle oyununun müzik versiyonu">
<meta name="twitter:description"
content="Girişini dinleyerek günün şarkıyı mümkün olduğunca az denemede tahmin edin.">
<meta name="twitter:image" content="%REACT_APP_PUBLIC_URL%/og-image-1200-600.jpg?%REACT_APP_VERSION%">
<meta name="twitter:image:type" content="image/jpg" />
<meta name="twitter:image:width" content="1200" />
<meta name="twitter:image:height" content="600" />
<meta name="twitter:image:alt" content="Heardle Türkçe ile günlük yayınlanan şarkıları tahmin et!" />

<meta name="twitter:text:app_country" content="TR" />
<meta name="twitter:text:app_name" content="Heardle Türkçe" />

<link rel="canonical" href="%REACT_APP_PUBLIC_URL%/">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

<title>Heardle Türkçe</title>

<script defer src="https://w.soundcloud.com/player/api.js"></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-5SRJGZZ5XG"></script>
<script async src="https://www.googletagmanager.com/gtag/js?id=%REACT_APP_GOOGLE_TM%"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'G-5SRJGZZ5XG');
gtag('config', '%REACT_APP_GOOGLE_TM%');
</script>
</head>

Expand All @@ -75,4 +27,4 @@
-->
</body>

</html>
</html>
71 changes: 71 additions & 0 deletions public/locales/tr/translation.json
@@ -0,0 +1,71 @@
{
"app": "HeardleTr",
"title": "Heardle Türkçe",
"PUBLIC_URL": "",
"loading": "Yükleniyor...",
"meta": {
"alt": "Heardle Türkçe ile günlük yayınlanan şarkıları tahmin et!",
"description": "Heardle Türkçe | Günlük olarak yayınlanan şarkıları tahmin edip sonuçlarını paylasabileceginiz basit bir web oyunudur.",
"description2": "Girişini dinleyerek günün şarkıyı mümkün olduğunca az denemede tahmin edin.",
"name": "Heardle Türkçe - Wordle oyununun müzik versiyonu"
},
"how_to_play": {
"title": "Nasıl oynanır",
"part1": "Şarkı girişini dinleyerek sanatçı ve şarkıyı tahmin edin.",
"part2": "Tahminlerinizi arama kutusuna yazip cikan sonuclardan birini sectikten sonra gonder butonuna basin.",
"part3": "Yanlış veya atlanan tahminler şarkının bir sonraki kısmını açar.",
"part4": "Mümkün olduğunca az denemede doğru tahmin edin ve puanınızı paylaşın.",
"start": "başla",
"part5": "Bu uygulamayı kullanarak, kullanıcı deneyiminizi artırmak amaçlı",
"and": "ve",
"part6": "teknolojilerini kullanmayı kabul etmiş olursunuz.",
"close": "Kapat"
},
"about": {
"title": "Uygulama Hakkında",
"part1": "uygulamasının Türkçe kopyasıdır.",
"part2": "Şarkılar her gün rastgele olacak şekilde seçilmektedir.",
"part3": "ya da o anki ruh haline gore",
"part4": "Bu uygulamada şarkıları çalınan sanatçılara ve tüm telif hakki sahiplerine sevgiler.",
"part5": "Kullanılan araç ve servisler",
"developer": "Geliştirici",
"close": "Kapat"
},
"music_player": {
"placeholder": "Tahmin ettiğiniz şarkıyı/sanatçıyı aratıp seçin.",
"skip": "İlerlet",
"finish": "Bitir",
"send": "Tahmin Gönder",
"help": "Arama yapmak icin en az 3 karakter girmelisiniz",
"no_results": "Sonuc bulunamadi",
"skipped": "İLERLETİLDİ",
"loading": "player yukleniyor...",
"stop": "Şarkıyı durdur",
"start": "Şarkıyı başlat"
},
"game_results": {
"one": "🏆 İşte aradığımız yetenek! 🏆",
"two": "Harikasın, bu kadar kısa sürede bildiğin için tebrikler 🥳",
"three": "Oooo, hizlisin ya da çok iyi bildiğin yerden çıktı sanırım? 😁",
"four": "Harikasın, bu kadar kısa sürede bildiğin için tebrikler 🥳",
"five": "Supersin! belki bir sonraki sefere çok daha iyi bildiğin bi şarkı gelir ?",
"six": "Kusura bakma daha fazla ipucu veremezdim 😜",
"failed": "Sanırım hiç ilgi alanın olmayan bir türe denk geldin... Yarın görüşürüz 😃",
"copy": "Sonucu Kopyala",
"copied": "Kopyalandi",
"share": "Twitter'da Paylaş",
"playlist": "Daha önce çıkan tüm şarkılar - Spotify",
"twitter": "{{icons}} \n #HeardleTr {{todayStr}} \n \n {{HEARDLE_TR_WEB_URL}}",
"soundcloud_title": "SoundCloud uzerinden {{trackName}} dinle",
"spotify_title": "Spotify uzerinden \"{{trackName}}\" dinle"
},
"timer": {
"hours": "saat",
"minutes": "dakika",
"seconds": "saniye",
"h": "s",
"m": "d",
"s": "s",
"next": "Sonraki Heardle"
}
}
42 changes: 28 additions & 14 deletions src/App.tsx
@@ -1,3 +1,5 @@
import { Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import Header from "./components/Header";
import PlayerContainer from "./components/player/PlayerContainer";
import AllModals from "./components/modals/AllModals";
Expand All @@ -7,7 +9,6 @@ import { useEffect, useState } from "react";
import { getDailySong } from "./components/utils/dataService";
import { SongConfig } from "./components/game/Models";


const APP_VERSION = process.env.REACT_APP_VERSION || "0"
console.debug("v" + APP_VERSION);

Expand All @@ -23,11 +24,11 @@ const EMPTY_SONG_CONFIG: SongConfig = {
others: []
}


function App() {
function Page() {

const [loading, setLoading] = useState(true);
const [currentSongConfig, setCurrentSongConfig] = useState<SongConfig>(EMPTY_SONG_CONFIG);
const { i18n } = useTranslation();

useEffect(() => {

Expand All @@ -44,23 +45,36 @@ function App() {
<Header />
<AllModals />
</ModalContextProvider>

<GameContextProvider>
{
loading ?
<>
<div className="max-w-screen-sm w-full mx-auto flex-col" >
<div className="text-center m-3 mt-6">
Yükleniyor...
</div>
</div>
.</>
: (
loading ? (
<Loader message={i18n.t('loading')} />
) : (
<PlayerContainer songConfig={currentSongConfig} />
)
}
</GameContextProvider>
</div>
);
}
};

export default App;
// loading component for suspense fallback
const Loader = (props: { message: any }) => (
<>
<div className="max-w-screen-sm w-full mx-auto flex-col" >
<div className="text-center m-3 mt-6">
{props.message}
</div>
</div>
.</>
);

// here app catches the suspense from page in case translations are not yet loaded
export default function App() {
return (
<Suspense fallback={<Loader message='Loading ...' />}>
<Page />
</Suspense>
);
};
109 changes: 58 additions & 51 deletions src/components/Header.jsx
@@ -1,9 +1,13 @@
import { useModalData } from "./modals/ModalContext";
import { useTranslation } from 'react-i18next';
import { HelmetProvider } from 'react-helmet-async';
import { Meta } from './Meta'

function Header() {

const { dispatch } = useModalData();

const { t } = useTranslation();

const openAbout = () => {
dispatch({ type: 'About' })
Expand All @@ -18,59 +22,62 @@ function Header() {
}

return (
<div className="flex-none">
<header className="border-b border-custom-line" role="banner">
<div className="max-w-screen-md mx-auto ">
<div className="flex justify-evenly text-custom-fgcolor p-3 items-center">
<div className="flex flex-1">
<button className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm"
onClick={openAbout} type="button" aria-label="Uygulama hakkında" title="Uygulama hakkında">
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</button>
{/* <button
className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm">
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
</path>
</svg>
</button> */}
</div>
<h1 className="font-serif text-3xl font-bold flex-grow text-center flex-2" alt="Heardle Türkçe">Heardle Türkçe</h1>
<div className="flex flex-1 justify-end">
{/* <button className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm"
onClick={openStats}>
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 20v-6M6 20V10M18 20V4"></path>
</svg>
</button> */}
<button
className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm"
onClick={openHowToPlay} type="button" aria-label="Nasıl oynanır" title="Nasıl oynanır">
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
</button>
<HelmetProvider>
<Meta />
<div className="flex-none">
<header className="border-b border-custom-line" role="banner">
<div className="max-w-screen-md mx-auto ">
<div className="flex justify-evenly text-custom-fgcolor p-3 items-center">
<div className="flex flex-1">
<button className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm"
onClick={openAbout} type="button" aria-label={t('about.title')} title={t('about.title')}>
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>
</button>
{/* <button
className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm">
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z">
</path>
</svg>
</button> */}
</div>
<h1 className="font-serif text-3xl font-bold flex-grow text-center flex-2" alt={t('title')}>{t('title')}</h1>
<div className="flex flex-1 justify-end">
{/* <button className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm"
onClick={openStats}>
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M12 20v-6M6 20V10M18 20V4"></path>
</svg>
</button> */}
<button
className="px-2 py-2 uppercase tracking-widest border-none flex items-center font-semibold text-sm"
onClick={openHowToPlay} type="button" aria-label={t('how_to_play.title')} title={t('how_to_play.title')}>
<svg
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
</svg>
</button>
</div>
</div>
</div>
</div>
</header>
</div>
</header>
</div>
</HelmetProvider>
);
}

export default Header;
export default Header;

0 comments on commit db73ddb

Please sign in to comment.