AngularJS Uygulamanızı Nasıl Uluslararasılaştırabilirsiniz?
Yayınlanan: 2022-03-11Uygulamanızı uluslararası hale getirmek, özellikle en baştan yapmaya başlamazsanız veya ona ister istemez bir yaklaşım benimserseniz, yazılım geliştirmeyi acı verici bir deneyim haline getirebilir.
Ön uç ve arka ucun birbirinden belirgin şekilde ayrı olduğu modern uygulamalar, uluslararasılaştırma söz konusu olduğunda başa çıkmak daha da zor olabilir. Birdenbire, bir zamanlar geleneksel sunucu tarafı sayfası tarafından oluşturulan web uygulamalarınızın uluslararası hale getirilmesine yardımcı olan, zamanla test edilmiş çok sayıda araca artık erişemezsiniz.
Buna göre, bir AngularJS uygulaması, kendisini uygun yerel ayarda işlemek için müşteriye teslim edilecek uluslararasılaştırma (i18n) ve yerelleştirme (l10n) verilerinin isteğe bağlı olarak teslim edilmesini gerektirir. Geleneksel sunucu tarafında oluşturulmuş uygulamaların aksine, zaten yerelleştirilmiş sayfaları sunmak için artık sunucuya güvenemezsiniz. Burada çok dilli bir PHP uygulaması oluşturma hakkında bilgi edinebilirsiniz.
Bu makalede, AngularJS uygulamanızı nasıl uluslararası hale getirebileceğinizi öğrenecek ve süreci kolaylaştırmak için kullanabileceğiniz araçlar hakkında bilgi edineceksiniz. AngularJS uygulamanızı çok dilli yapmak bazı ilginç zorluklar doğurabilir, ancak bazı yaklaşımlar bu zorlukların çoğundan kurtulmayı kolaylaştırabilir.
Basit Bir i18n Özellikli AngularJS Uygulaması
İstemcinin dili ve yerel ayarı kullanıcı tercihlerine göre anında değiştirmesine izin vermek için bir dizi önemli tasarım kararı vermeniz gerekecektir:
- Uygulamanızı en başından dil ve yerel ayardan bağımsız olacak şekilde nasıl tasarlarsınız?
- i18n ve l10n verilerini nasıl yapılandırırsınız?
- Bu verileri müşterilere verimli bir şekilde nasıl ulaştırırsınız?
- Geliştirici iş akışını basitleştirmek için düşük seviyeli uygulama ayrıntılarının çoğunu nasıl soyutlarsınız?
Bu soruları mümkün olduğunca erken yanıtlamak, geliştirme sürecindeki engellerden kaçınmaya yardımcı olabilir. Bu zorlukların her biri bu makalede ele alınacaktır; bazıları sağlam AngularJS kitaplıkları aracılığıyla, bazıları ise belirli stratejiler ve yaklaşımlar aracılığıyla.
AngularJS için Uluslararasılaştırma Kitaplıkları
AngularJS uygulamalarını uluslararası hale getirmek için özel olarak oluşturulmuş bir dizi JavaScript kitaplığı vardır.
angular-translate
, i18n verilerini eşzamansız olarak yükleme yeteneğinin yanı sıra filtreler ve yönergeler sağlayan bir AngularJS modülüdür. MessageFormat
aracılığıyla çoğullaştırmayı destekler ve son derece genişletilebilir ve yapılandırılabilir olacak şekilde tasarlanmıştır.
Projenizde angular-translate
kullanıyorsanız, aşağıdaki paketlerden bazılarını çok yararlı bulabilirsiniz:
-
angular-sanitize
: çevirilerde XSS saldırılarına karşı koruma sağlamak için kullanılabilir. -
angular-translate-interpolation-messageformat
: cinsiyete duyarlı metin biçimlendirme desteği ile çoğullaştırma. -
angular-translate-loader-partial
: çevrilmiş dizeleri istemcilere teslim etmek için kullanılır.
Gerçekten dinamik bir deneyim için, angular-dynamic-locale
ekleyebilirsiniz. Bu kitaplık, yerel ayarı dinamik olarak değiştirmenize olanak tanır ve bu, tarihlerin, sayıların, para birimlerinin vb. biçimlendirilme biçimini içerir.
Başlarken: İlgili Paketleri Yükleme
AngularJS standart şablonunuzun zaten hazır olduğunu varsayarsak, uluslararasılaştırma paketlerini kurmak için NPM'yi kullanabilirsiniz:
npm i -S angular-translate angular-translate-interpolation-messageformat angular-translate-loader-partial angular-sanitize messageformat
Paketler yüklendikten sonra modülleri uygulamanızın bağımlılıkları olarak eklemeyi unutmayın:
// /src/app/core/core.module.js app.module('app.core', ['pascalprecht.translate', ...]);
Modül adının paketin adından farklı olduğuna dikkat edin.
İlk Dizenizi Çevirmek
Uygulamanızın biraz metin içeren bir araç çubuğuna ve yer tutucu metin içeren bir alana sahip olduğunu varsayalım:
<nav class="navbar navbar-default"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">Hello</a> </div> <div class="collapse navbar-collapse"> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" ng-model="vm.query" placeholder="Search"> </div> ... </div> </div> </nav>
Yukarıdaki görünümde uluslararasılaştırabileceğiniz iki bit metin vardır: “Merhaba” ve “Ara”. HTML açısından, biri bağlantı etiketinin iç metni olarak görünürken, diğeri bir özelliğin değeri olarak görünür.
Bunları uluslararası hale getirmek için, sayfayı oluştururken kullanıcının tercihine bağlı olarak, AngularJS'nin daha sonra gerçek çevrilmiş dizelerle değiştirebileceği belirteçlerle her iki dize değişmezini değiştirmeniz gerekecektir.
AngularJS, sağladığınız çeviri tablolarında arama yapmak için belirteçlerinizi kullanarak bunu yapabilir. angular-translate
modülü, bu çeviri tablolarının düz JavaScript nesneleri veya JSON nesneleri (uzaktan yükleniyorsa) olarak sağlanmasını bekler.
Aşağıda, bu çeviri tablolarının genel olarak nasıl görüneceğine dair bir örnek verilmiştir:
// /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello", "SEARCH": "Search" } } // /src/app/toolbar/i18n/tr.json { "TOOLBAR": { "HELLO": "Merhaba", "SEARCH": "Ara" } }
Araç çubuğu görünümünü yukarıdan uluslararası hale getirmek için, dize değişmezlerini, AngularJS'nin çeviri tablosunda aramak için kullanabileceği belirteçlerle değiştirmeniz gerekir:
<!-- /src/app/toolbar/toolbar.html --> <a class="navbar-brand" href="#" translate="TOOLBAR.HELLO"></a> <!-- or --> <a class="navbar-brand" href="#">{{'TOOLBAR.HELLO' | translate}}</a>
İç metin için translate
yönergesini veya translate
filtresini nasıl kullanabileceğinize dikkat edin. ( translate
yönergesi hakkında buradan ve translate
filtreleri hakkında buradan daha fazla bilgi edinebilirsiniz.)
Bu değişikliklerle, görünüm oluşturulduğunda, angular-translate
, TOOLBAR.HELLO
karşılık gelen uygun çeviriyi mevcut dile göre DOM'ye otomatik olarak ekleyecektir.
Öznitelik değerleri olarak görünen dize değişmezlerini belirtmek için aşağıdaki yaklaşımı kullanabilirsiniz:
<!-- /src/app/toolbar/toolbar.html --> <input type="text" class="form-control" ng-model="vm.query" translate translate-attr-placeholder="TOOLBAR.SEARCH">
Şimdi, belirtilmiş dizeleriniz değişkenler içeriyorsa ne olur?
"Merhaba, {{name}}." gibi durumları ele almak için, AngularJS'nin zaten desteklediği aynı enterpolatör sözdizimini kullanarak değişken değiştirme gerçekleştirebilirsiniz:
Çeviri tablosu:
// /src/app/toolbar/i18n/en.json { "TOOLBAR": { "HELLO": "Hello, {{name}}." } }
Daha sonra değişkeni çeşitli şekillerde tanımlayabilirsiniz. Burda biraz var:
<!-- /src/app/toolbar/toolbar.html --> <a ... translate="TOOLBAR.HELLO" translate-values='{ name: vm.user.name }'></a> <!-- or --> <a ... translate="TOOLBAR.HELLO" translate-value-name='{{vm.user.name}}'></a> <!-- or --> <a ...>{{'TOOLBAR.HELLO | translate:'{ name: vm.user.name }'}}</a>
Çoğullaştırma ve Cinsiyetle Başa Çıkmak
i18n ve l10n söz konusu olduğunda çoğullaştırma oldukça zor bir konudur. Farklı diller ve kültürler, bir dilin çeşitli durumlarda çoğullaştırmayı nasıl ele aldığı konusunda farklı kurallara sahiptir.
Bu zorluklar nedeniyle, yazılım geliştiriciler bazen sorunu basitçe ele almayacaklar (veya en azından yeterince ele almayacaklar), bu da aşağıdaki gibi aptalca cümleler üreten yazılımlara yol açacaktır:
He saw 1 person(s) on floor 1. She saw 1 person(s) on floor 3. Number of people seen on floor 2: 2.
Neyse ki, bununla nasıl başa çıkılacağına dair bir standart var ve standardın JavaScript uygulaması MessageFormat olarak mevcut.
MessageFormat ile, yukarıdaki kötü yapılandırılmış cümleleri aşağıdakilerle değiştirebilirsiniz:
He saw 1 person on the 2nd floor. She saw 1 person on the 3rd floor. They saw 2 people on the 5th floor.
MessageFormat
aşağıdaki gibi ifadeleri kabul eder:
var message = [ '{GENDER, select, male{He} female{She} other{They}}', 'saw', '{COUNT, plural, =0{no one} one{1 person} other{# people}}', 'on the', '{FLOOR, selectordinal, one{#st} two{#nd} few{#rd} other{#th}}', 'floor.' ].join(' ');
Yukarıdaki diziyle bir biçimlendirici oluşturabilir ve onu dizeler oluşturmak için kullanabilirsiniz:
var messageFormatter = new MessageFormat('en').compile(message); messageFormatter({ GENDER: 'male', COUNT: 1, FLOOR: 2 }) // 'He saw 1 person on the 2nd floor.' messageFormatter({ GENDER: 'female', COUNT: 1, FLOOR: 3 }) // 'She saw 1 person on the 3rd floor.' messageFormatter({ COUNT: 2, FLOOR: 5 }) // 'They saw 2 people on the 5th floor.'
Uygulamalarınızda tam işlevselliğinden yararlanmak için MessageFormat
angular-translate
ile nasıl kullanabilirsiniz?
Uygulama yapılandırmanızda, angular-translate
mesaj formatı enterpolasyonunun aşağıdaki gibi mevcut olduğunu söylemeniz yeterlidir:
/src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); });
Çeviri tablosundaki bir giriş şu şekilde görünebilir:
// /src/app/main/social/i18n/en.json { "SHARED": "{GENDER, select, male{He} female{She} other{They}} shared this." }
Ve görünümde:
<!-- /src/app/main/social/social.html --> <div translate="SHARED" translate-values="{ GENDER: 'male' }" translate-interpolation="messageformat"></div> <div> {{ 'SHARED' | translate:"{ GENDER: 'male' }":'messageformat' }} </div>
Burada, AngularJS'deki varsayılan enterpolatör yerine mesaj formatı enterpolatörünün kullanılması gerektiğini açıkça belirtmelisiniz. Bunun nedeni, iki enterpolatörün sözdizimlerinde biraz farklı olmasıdır. Bununla ilgili daha fazla bilgiyi buradan okuyabilirsiniz.
Uygulamanıza Çeviri Tabloları Sağlama
Artık AngularJS'nin çeviri tablolarından belirteçleriniz için çevirileri nasıl arayabileceğini bildiğinize göre, uygulamanız ilk etapta çeviri tablolarını nasıl biliyor? Uygulamanıza hangi yerel ayarın/dilin kullanılması gerektiğini nasıl söylersiniz?
$translateProvider
hakkında bilgi edineceğiniz yer burasıdır.
Desteklemek istediğiniz her yerel ayar için çeviri tablolarını doğrudan uygulamanızın core.config.js
dosyasında aşağıdaki gibi sağlayabilirsiniz:

// /src/app/core/core.config.js app.config(function ($translateProvider) { $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); $translateProvider.translations('en', { TOOLBAR: { HELLO: 'Hello, {{name}}.' } }); $translateProvider.translations('tr', { TOOLBAR: { HELLO: 'Merhaba, {{name}}.' } }); $translateProvider.preferredLanguage('en'); });
Burada İngilizce (en) ve Türkçe (tr) için JavaScript nesneleri olarak çeviri tabloları sağlarken, mevcut dili İngilizce (en) olarak ilan ediyorsunuz. Kullanıcı dili değiştirmek isterse, bunu $translate hizmetiyle yapabilirsiniz:
// /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, $translate) { $scope.changeLanguage = function (languageKey) { $translate.use(languageKey); // Persist selection in cookie/local-storage/database/etc... }; });
Hala varsayılan olarak hangi dilin kullanılması gerektiği sorusu var. Uygulamamızın başlangıç dilini sabit kodlamak her zaman kabul edilebilir olmayabilir. Bu gibi durumlarda, bir alternatif, dili $translateProvider kullanarak otomatik olarak belirlemeye çalışmaktır:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.determinePreferredLanguage(); });
determinePreferredLanguage
, window.navigator
içindeki değerleri arar ve kullanıcı tarafından net bir sinyal sağlanana kadar akıllı bir varsayılan seçer.
Tembel Yüklenen Çeviri Tabloları
Önceki bölüm, çeviri tablolarını doğrudan kaynak kodunda JavaScript nesneleri olarak nasıl sağlayabileceğinizi gösterdi. Bu, küçük uygulamalar için kabul edilebilir, ancak yaklaşım ölçeklenebilir değildir, bu nedenle çeviri tabloları genellikle uzak bir sunucudan JSON dosyaları olarak indirilir.
Çeviri tablolarının bu şekilde bakımı, müşteriye teslim edilen ilk yük boyutunu azaltır, ancak ek karmaşıklık getirir. Şimdi, i18n verilerini müşteriye teslim etme tasarım zorluğuyla karşı karşıyasınız. Bu dikkatli bir şekilde ele alınmazsa, uygulamanızın performansı gereksiz yere düşebilir.
Neden bu kadar karmaşık? AngularJS uygulamaları modüller halinde düzenlenmiştir. Karmaşık bir uygulamada, her biri kendi farklı i18n verilerine sahip birçok modül olabilir. Bu nedenle, i18n verilerinin hepsini aynı anda yüklemek ve sağlamak gibi naif bir yaklaşımdan kaçınılmalıdır.
İhtiyacınız olan şey, i18n verilerinizi modüle göre düzenlemenin bir yoludur. Bu, ihtiyaç duyduğunuzda tam olarak ihtiyacınız olanı yüklemenize ve aynı verileri yeniden yüklemekten kaçınmak için (en azından önbellek geçersiz olana kadar) daha önce yüklenenleri önbelleğe almanıza olanak tanır.
ParsiyelLoader burada partialLoader
giriyor.
Diyelim ki uygulamanızın çeviri tabloları şöyle yapılandırılmış:
/src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json
$translateProvider
, bu yapıyla eşleşen bir URL modeliyle partialLoader
kullanacak şekilde yapılandırabilirsiniz:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoader('$translatePartialLoader', { urlTemplate: '/src/app/{part}/i18n/{lang}.json' }); });
Tahmin edilebileceği gibi, "lang" çalışma zamanında dil koduyla değiştirilir (örneğin "en" veya "tr"). Peki ya "parça"? $translateProvider hangi "parçanın" yükleneceğini nasıl biliyor?
Bu bilgiyi kontrolörlerin içinde $translatePartialLoader
ile sağlayabilirsiniz:
// /src/app/main/main.controller.js app.controller('MainCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('main'); }); // /src/app/toolbar/toolbar.config.js app.controller('ToolbarCtrl', function ($translatePartialLoader) { $translatePartialLoader.addPart('toolbar'); });
Model artık tamamlanmıştır ve belirli bir görünüm için i18n verileri, denetleyicisi ilk çalıştırıldığında yüklenir, bu tam olarak istediğiniz şeydir.
Önbelleğe Alma: Yükleme Sürelerini Azaltma
Peki ya önbelleğe alma?
Uygulama yapılandırmasında standart önbelleği $translateProvider
ile etkinleştirebilirsiniz:
// /src/app/core/core.config.js app.config(function ($translateProvider) { ... $translateProvider.useLoaderCache(true); // default is false });
Belirli bir dil için önbelleği bozmanız gerekirse, $translate
öğesini kullanabilirsiniz:
$translate.refresh(languageKey); // omit languageKey to refresh all
Bu parçalar yerinde olduğunda, uygulamanız tamamen uluslararası hale getirilir ve birden çok dili destekler.
Numaraları, Para Birimlerini ve Tarihleri Yerelleştirme
Bu bölümde, bir AngularJS uygulamasında sayılar, para birimleri, tarihler ve benzeri gibi UI öğelerinin biçimlendirilmesini desteklemek için angular-dynamic-locale
nasıl kullanabileceğinizi öğreneceksiniz.
Bunun için iki paket daha kurmanız gerekecek:
npm i -S angular-dynamic-locale angular-i18n
Paketler yüklendikten sonra modülü uygulamanızın bağımlılıklarına ekleyebilirsiniz:
// /src/app/core/core.module.js app.module('app.core', ['tmh.dynamicLocale', ...]);
Yerel Kurallar
Yerel ayar kuralları, tarihlerin, sayıların, para birimlerinin ve benzerlerinin $locale hizmetine bağlı bileşenler tarafından nasıl biçimlendirilmesi gerektiğine ilişkin belirtimler sağlayan basit JavaScript dosyalarıdır.
Şu anda desteklenen yerel ayarların listesi burada mevcuttur.
İşte angular-locale_en-us.js
ay ve tarih biçimlendirmesini gösteren bir pasaj:
... "MONTH": [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ], "SHORTDAY": [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], ...
i18n verilerinden farklı olarak, yerel ayar kuralları uygulama için geneldir ve belirli bir yerel ayar için kuralların hepsinin bir kerede yüklenmesini gerektirir.
Varsayılan olarak, angular-dynamic-locale
, yerel ayar kuralları dosyalarının angular/i18n/angular-locale_{{locale}}.js
içinde bulunmasını bekler. Başka bir yerde bulunuyorlarsa, varsayılanı geçersiz kılmak için tmhDynamicLocaleProvider
kullanılmalıdır:
// /src/app/core/core.config.js app.config(function (tmhDynamicLocaleProvider) { tmhDynamicLocaleProvider.localeLocationPattern( '/node_modules/angular-i18n/angular-locale_{{locale}}.js'); });
Önbelleğe alma, tmhDynamicLocaleCache
hizmeti tarafından otomatik olarak gerçekleştirilir.
Yerel ayar kurallarının dize çevirilerinden daha az değişme olasılığı daha düşük olduğundan, önbelleği geçersiz kılmak burada daha az endişe vericidir.
Yerel ayarlar arasında geçiş yapmak için angular-dynamic-locale
, tmhDynamicLocale
hizmetini sağlar:
// /src/app/toolbar/toolbar.controller.js app.controller('ToolbarCtrl', function ($scope, tmhDynamicLocale) { $scope.changeLocale = function (localeKey) { tmhDynamicLocale.set(localeKey); // Persist selection in cookie/local-storage/database/etc... }; });
Otomatik Çeviri ile Çeviri Tabloları Oluşturma
Yerel ayar kuralları angular-i18n
paketiyle birlikte gönderilir, bu nedenle tek yapmanız gereken paket içeriğini gerektiği gibi uygulamanız için kullanılabilir hale getirmektir. Ancak çeviri tablolarınız için JSON dosyalarını nasıl oluşturursunuz? Tam olarak indirip uygulamamıza bağlayabileceğiniz bir paket yok.
Bir seçenek, özellikle uygulamanızdaki dizeler değişkenler veya çoğul ifadeler içermeyen basit değişmezler ise, programlı çeviri API'lerini kullanmaktır.
Gulp ve birkaç ekstra paketle, uygulamanız için programlı çeviriler istemek çok kolay:
import gulp from 'gulp'; import map from 'map-stream'; import rename from 'gulp-rename'; import traverse from 'traverse'; import transform from 'vinyl-transform'; import jsonFormat from 'gulp-json-format'; function translateTable(to) { return transform(() => { return map((data, done) => { const table = JSON.parse(data); const strings = []; traverse(table).forEach(function (value) { if (typeof value !== 'object') { strings.push(value); } }); Promise.all(strings.map((s) => getTranslation(s, to))) .then((translations) => { let index = 0; const translated = traverse(table).forEach(function (value) { if (typeof value !== 'object') { this.update(translations[index++]); } }); done(null, JSON.stringify(translated)); }) .catch(done); }); }); } function translate(to) { return gulp.src('src/app/**/i18n/en.json') .pipe(translateTable(to)) .pipe(jsonFormat(2)) .pipe(rename({ basename: to })) .pipe(gulp.dest('src/app')); } gulp.task('translate:tr', () => translate('tr')); This task assumes the following folder structure: /src/app/main/i18n/en.json /src/app/toolbar/i18n/en.json /src/app/navigation/i18n/en.json ...
Komut dosyası önce tüm İngilizce çeviri tablolarını okur, dize kaynakları için eşzamansız olarak çeviriler ister ve ardından yeni bir dilde bir çeviri tablosu oluşturmak için İngilizce dizeleri çevrilmiş dizelerle değiştirir.
Son olarak, yeni çeviri tablosu İngilizce çeviri tablosuna bir kardeş olarak yazılır ve şu sonucu verir:
/src/app/main/i18n/en.json /src/app/main/i18n/tr.json /src/app/toolbar/i18n/en.json /src/app/toolbar/i18n/tr.json /src/app/navigation/i18n/en.json /src/app/navigation/i18n/tr.json ...
getTranslation
uygulaması da basittir:
import bluebird from 'bluebird'; import MicrosoftTranslator from 'mstranslator'; bluebird.promisifyAll(MicrosoftTranslator.prototype); const Translator = new MicrosoftTranslator({ client_id: process.env.MICROSOFT_TRANSLATOR_CLIENT_ID, client_secret: process.env.MICROSOFT_TRANSLATOR_CLIENT_SECRET }, true); function getTranslation(string, to) { const text = string; const from = 'en'; return Translator.translateAsync({ text, from, to }); }
Burada Microsoft Çeviri kullanıyoruz, ancak Google Çeviri veya Yandex Çeviri gibi başka bir sağlayıcıyı kolayca kullanabiliriz.
Programlı çeviriler kullanışlı olsa da, aşağıdakiler de dahil olmak üzere çeşitli dezavantajlar vardır:
- Robot çevirileri kısa dizeler için iyidir, ancak o zaman bile farklı bağlamlarda farklı anlamlara sahip kelimelerle ilgili tuzaklar olabilir (örneğin, “havuz” yüzme veya gruplama anlamına gelebilir).
- API'ler, ileti biçimine dayanan değişkenler veya dizeler içeren dizeleri işleyemeyebilir.
Bu ve diğer durumlarda, insan çevirileri gerekebilir; ancak, bu başka bir blog yazısının konusu.
Ön Uçları Uluslararasılaştırmak Sadece Göz Korkutucu Görünüyor
Bu makalede, AngularJS uygulamalarını uluslararası hale getirmek ve yerelleştirmek için bu paketleri nasıl kullanacağınızı öğrendiniz.
angular-translate
, angular-dynamic-locale
ve gulp
, ağrılı düşük seviyeli uygulama ayrıntılarını kapsayan bir AngularJS uygulamasını uluslararası hale getirmek için güçlü araçlardır.
Bu gönderide tartışılan fikirleri gösteren bir demo uygulaması için bu GitHub deposuna göz atın.