Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa4a4b3e27 | |||
| b776ebbd26 | |||
| 0d3a514456 | |||
| 99e97edfd1 | |||
| db60909348 | |||
| 080c39ac88 | |||
| c3f343ad70 | |||
| fb1224f7c9 | |||
| f68bc87031 | |||
| fee4c8fa78 | |||
| e8352f599b | |||
| 80de8dc6f9 | |||
| e4e7599cb2 | |||
| 88c1657b6a | |||
| f195d22342 | |||
| 864f80c7b8 | |||
| d245055bf4 | |||
| 9c4dc0ff11 | |||
| 092eb09316 | |||
| 82ea32cd33 | |||
| 2ee018c37c | |||
| 81690db06d | |||
| a9c872e5b4 | |||
| 97e95efb17 | |||
| 181ec53ef0 | |||
| 0a3feba23a | |||
| f099d8f46a | |||
| 36ec1823c8 | |||
| 23f24e426b | |||
| cf4572cb03 | |||
| efa575a920 | |||
| fc2c99f0c2 | |||
| 3235a700c9 | |||
| e23ac8c346 | |||
| 93e45655eb | |||
| 1b0800beb6 | |||
| d828df82bb | |||
| 690ee11336 | |||
| c719e58d4e | |||
| 7e2cd6eff4 | |||
| 0bdf0c3394 | |||
| d7200111e2 | |||
| faf752dd3c | |||
| 31e695cd3a | |||
| f77866faa5 | |||
| fbbed52a3d | |||
| e7ad0123ac | |||
| f34fc0f2a0 | |||
| 9e6bcc22b6 | |||
| 3c023ba588 | |||
| ddbfc7e3a7 | |||
| 59d9dfe40d | |||
| f6091f1bb8 | |||
| 57d092c0a3 | |||
| 0649d38c48 | |||
| aa48ce7890 | |||
| e24b4bdf41 | |||
| 76081f0e85 | |||
| e0941fe931 | |||
| 1ef535d132 | |||
| b7ef2450bd | |||
| 38b06f9d1c | |||
| 5bc2fd0029 | |||
| d39acfc582 | |||
| 5a1dfb51a2 | |||
| af2b57c6d1 | |||
| dd0bee9355 | |||
| af147f5fea | |||
| 3c92a81cfe | |||
| 05e8059b5d | |||
| 5848cd5189 | |||
| c68e95631b | |||
| 8c4c58e094 | |||
| b7434e12c6 | |||
| 121ba7d9c0 | |||
| e054b04b06 | |||
| fe3d88c467 | |||
| 01a708b054 | |||
| bd1ccb29c0 | |||
| 8a08a0a89a | |||
| 5d88241595 | |||
| 4ec2683317 | |||
| 6fb2bfb265 | |||
| 5f6c468a4b | |||
| 48ae773e85 | |||
| 4e98034536 | |||
| f2b5a29b15 | |||
| 3d2f2ab64e | |||
| 7d7c018496 | |||
| 92f14b2f4f | |||
| bc59a32244 | |||
| 48f03cb83b | |||
| 16f9d5a58a | |||
| a4bc64527c | |||
| 910e912b05 | |||
| ba98e17d2d | |||
| abeed5ae88 | |||
| c28450b97d | |||
| 40fbb750db | |||
| e9ddc3d332 | |||
| 98355a5286 | |||
| 5fc0462ab1 | |||
| 81b94e8635 | |||
| 92af30f61b | |||
| 3cd5ee9e99 | |||
| 8bf36c7d39 | |||
| a9fe4599cd | |||
| f4500a490b | |||
| 547b6efc6c | |||
| c5a33f4d18 | |||
| 15ba98e14d | |||
| 0fe2775420 | |||
| f69e7bae7b | |||
| c1dc4629c0 | |||
| 92f9147e80 | |||
| 809f796f56 | |||
| 3b83f01952 | |||
| 654a7ed175 | |||
| b4a81d703a | |||
| afa92d8f4a | |||
| a037917f16 | |||
| 22c5e40cfc | |||
| 1b920e2b5b | |||
| 8c00956f29 | |||
| 3ed63cacb3 | |||
| b36ccf298b | |||
| 2d6af94c88 | |||
| dd013a471d | |||
| 79ca5d49b6 | |||
| 5bf876d135 | |||
| 6665ed82b4 | |||
| c668435b15 | |||
| 6abec883c5 | |||
| 1da2e3771a | |||
| a94fb17211 | |||
| a61c80ee3d | |||
| 72c1234b5c | |||
| 2b15c17e60 | |||
| 45c9fa24d0 | |||
| c2767f031a | |||
| a484b19378 | |||
| 7180d44f6d | |||
| 0c51fecef8 | |||
| 945c3ca2ec | |||
| 945e250f17 | |||
| fed91996fc | |||
| d1c2ccd985 | |||
| 3fb2106707 | |||
| 7ae1dcfab8 | |||
| b2e6f89bcb | |||
| 53a265844f | |||
| 607763ff52 | |||
| 752876fed9 | |||
| 08c1e28427 | |||
| 7e3700a2c2 | |||
| 61cc5c8dda | |||
| 11cb89c944 | |||
| ead0077fde | |||
| f0ab100b07 | |||
| c195ff79d2 | |||
| 474882ba7c | |||
| ddf9640222 | |||
| 6d9ffc808b | |||
| 8675325f50 | |||
| b678e7f833 | |||
| ab08b6b840 | |||
| d97a9ed69e | |||
| f75632d0e8 | |||
| d24c9e4502 | |||
| 506a98163a | |||
| 2018860c23 | |||
| cc6ca1f660 | |||
| d6bfc44646 | |||
| 42c3b3ac45 | |||
| 69710a71b9 | |||
| 4150931ac8 | |||
| c82dd5c6b6 | |||
| a9d6f17e3c | |||
| 502460be79 | |||
| cda0bb49bc | |||
| 55efbc6614 | |||
| 62540c4a98 | |||
| a79d00a8dd | |||
| b51cf5876f | |||
| 31a79ca9dd | |||
| 0be000d474 | |||
| 6a4a0101a5 | |||
| b487613b71 | |||
| a132da44f9 | |||
| da0ee2477b | |||
| 1eab2c856b | |||
| 1b00a3120e | |||
| af36454b8f | |||
| fd30e5d1b7 | |||
| dbb9853066 | |||
| 0ca112b28d | |||
| 34f790cc10 | |||
| 15896248c4 | |||
| 33c7b6a51e | |||
| 276e4b30ca | |||
| 187f8c3812 | |||
| ad4f04b005 | |||
| e18c8bbbcf | |||
| ae3a697170 | |||
| e9f737ab1d | |||
| 751b9339f0 | |||
| f35632ee0d | |||
| e232d25fc8 | |||
| bc65bb10ef | |||
| 3c5c4c5e2a | |||
| dbadfee9ed | |||
| 51ae8fdd9c | |||
| 4168e1c8d4 | |||
| 575fdad12c | |||
| 19c9202fc1 | |||
| 29ddc24602 | |||
| fc0ebf1a28 | |||
| bbe26d22af |
@@ -0,0 +1,85 @@
|
||||
---
|
||||
description: Google yapılandırılmış veri (JSON-LD) standartları
|
||||
globs: app/Support/*StructuredData*.php, app/Http/Controllers/**/*.php, resources/views/**/*.blade.php
|
||||
---
|
||||
# Yapılandırılmış Veri (Structured Data) Kuralları
|
||||
|
||||
Google [Yapılandırılmış Veri Genel Yönergeleri](https://developers.google.com/search/docs/appearance/structured-data/sd-policies?hl=tr) ve [Giriş](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data?hl=tr) dokümanlarına uy.
|
||||
|
||||
## Temel İlkeler
|
||||
|
||||
1. **JSON-LD kullan** — Google'ın önerdiği format; `<head>` içinde `<script type="application/ld+json">` ile sunulur.
|
||||
2. **Sayfa içeriğiyle eşleş** — İşaretlenen veri kullanıcının gördüğü içerikle birebir uyumlu olmalı; görünmeyen veya yanlış bilgi ekleme.
|
||||
3. **Eksiksiz ve doğru özellikler** — Zorunlu alanları doldur; eksik/hatalı önerilen alanları doldurmaya çalışmak yerine az ama doğru veri tercih et.
|
||||
4. **schema.org sözlüğü** — `@context: https://schema.org` kullan; Google Arama özellikleri için [Search Central özellik rehberlerini](https://developers.google.com/search/docs/appearance/structured-data/search-gallery?hl=tr) referans al.
|
||||
|
||||
## Proje Mimarisi
|
||||
|
||||
```
|
||||
app/Support/
|
||||
├── StructuredData.php # Ortak yardımcılar (WebSite, Organization, WebPage, BreadcrumbList)
|
||||
├── PageStructuredData.php # CMS sayfaları
|
||||
├── BlogStructuredData.php # Blog listesi ve detay
|
||||
└── MusicProductionStructuredData.php
|
||||
```
|
||||
|
||||
### Controller → View akışı
|
||||
|
||||
```php
|
||||
// Controller
|
||||
'structuredData' => BlogStructuredData::forShow($post, url()->current()),
|
||||
|
||||
// Layout (layouts/site.blade.php) otomatik render eder:
|
||||
@if(!empty($structuredData))
|
||||
<x-seo.json-ld :data="$structuredData" />
|
||||
@endif
|
||||
```
|
||||
|
||||
**Asla** Blade içinde ham JSON string interpolasyonu kullanma — `json_encode` ile `<x-seo.json-ld>` bileşenini kullan.
|
||||
|
||||
## Sayfa Türü → Schema Eşlemesi
|
||||
|
||||
| Sayfa türü | Schema türleri | Sınıf |
|
||||
|---|---|---|
|
||||
| Ana sayfa / CMS sayfaları | WebSite, Organization, WebPage, BreadcrumbList | `PageStructuredData` |
|
||||
| Blog listesi | CollectionPage, ItemList, BlogPosting (özet) | `BlogStructuredData::forIndex` |
|
||||
| Blog detay | BlogPosting, WebPage, BreadcrumbList | `BlogStructuredData::forShow` |
|
||||
| Müzik prodüksiyonları | CollectionPage / MusicAlbum, ItemList | `MusicProductionStructuredData` |
|
||||
| Ürün/hizmet sayfaları | WebPage, Product veya Service | Henüz eklenmedi |
|
||||
| Kariyer / staj | WebPage | Henüz eklenmedi |
|
||||
| İletişim | WebPage, Organization (contactPoint) | Henüz eklenmedi |
|
||||
|
||||
## @graph Deseni
|
||||
|
||||
Birden fazla entity için `@graph` kullan; `@id` ile cross-reference yap:
|
||||
|
||||
```php
|
||||
return self::wrap([
|
||||
self::websiteNode(),
|
||||
self::organizationNode(),
|
||||
self::webPageNode($pageUrl, $name, $description),
|
||||
self::breadcrumbNode($pageUrl, $items),
|
||||
// sayfa-özel entity (BlogPosting, MusicAlbum, vb.)
|
||||
]);
|
||||
```
|
||||
|
||||
## Yeni Sayfa Eklerken
|
||||
|
||||
1. `app/Support/` altında `*StructuredData.php` sınıfı oluştur veya mevcut sınıfı genişlet.
|
||||
2. `StructuredData` base sınıfındaki ortak node'ları kullan (DRY).
|
||||
3. Controller'da `structuredData` key'ini view'a geçir.
|
||||
4. Görünür içerikle eşleştiğini doğrula (başlık, açıklama, görsel, tarih).
|
||||
5. [Zengin Sonuçlar Testi](https://search.google.com/test/rich-results) ile doğrula.
|
||||
|
||||
## Yasaklar
|
||||
|
||||
- ❌ Boş veya içeriksiz sayfalara yalnızca schema eklemek
|
||||
- ❌ Blade'de `"headline": "{{ $title }}"` gibi kaçışsız JSON
|
||||
- ❌ `@push('scripts')` ile body sonuna JSON-LD koymak (head'de olmalı)
|
||||
- ❌ data-vocabulary.org işaretlemesi
|
||||
- ❌ Kullanıcıya görünmeyen rating/review verisi uydurmak
|
||||
|
||||
## Doğrulama
|
||||
|
||||
- Geliştirme: [Zengin Sonuçlar Testi](https://search.google.com/test/rich-results)
|
||||
- Prod: Search Console → Zengin sonuçlar raporları
|
||||
@@ -63,3 +63,24 @@ AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
|
||||
# Spotify Web API (Müzik Prodüksiyonları senkronizasyonu)
|
||||
# NOT: Developer Dashboard'da uygulamayı oluşturan Spotify hesabında Premium abonelik gerekir.
|
||||
SPOTIFY_CLIENT_ID=
|
||||
SPOTIFY_CLIENT_SECRET=
|
||||
SPOTIFY_ARTIST_ID=
|
||||
SPOTIFY_MARKET=TR
|
||||
|
||||
# YouTube Data API v3 (Müzik Prodüksiyonları / Art Track senkronizasyonu)
|
||||
# Ana kanal (@handle) — Videolar sekmesi
|
||||
YOUTUBE_CHANNEL_ID=
|
||||
# Topic kanalı — Yayınlananlar sekmesi (ZORUNLU önerilir, otomatik arama güvenilir değil)
|
||||
YOUTUBE_TOPIC_CHANNEL_ID=UCEGzDgiExoGrwWEnpIdOGRA
|
||||
# Sunucu/cron senkronizasyonu için KISITLAMASIZ veya IP kısıtlı ayrı bir anahtar kullanın.
|
||||
YOUTUBE_API_KEY=
|
||||
# İsteğe bağlı: playlist ID doğrudan (Topic uploads: UU + topic channel ID'den sonraki kısım)
|
||||
YOUTUBE_PLAYLIST_ID=
|
||||
# İsteğe bağlı: yalnızca DistroKid yayınları için açıklama filtresi
|
||||
# YOUTUBE_DISTRIBUTOR_FILTER=Provided to YouTube by DistroKid
|
||||
# Sadece referrer kısıtlı web anahtarı kullanıyorsanız:
|
||||
# YOUTUBE_API_REFERER=https://truncgil.com
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\MusicProduction;
|
||||
use App\Services\SpotifyService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SyncSpotifyMusicProductions extends Command
|
||||
{
|
||||
protected $signature = 'spotify:sync-music-productions
|
||||
{--artist= : Override Spotify artist ID}
|
||||
{--dry-run : Show changes without writing to database}';
|
||||
|
||||
protected $description = 'Spotify sanatçı hesabından albüm ve single verilerini müzik prodüksiyonlarına senkronize eder';
|
||||
|
||||
public function handle(SpotifyService $spotifyService): int
|
||||
{
|
||||
if ($this->option('artist')) {
|
||||
$spotifyService = new SpotifyService(
|
||||
artistId: $this->option('artist'),
|
||||
);
|
||||
}
|
||||
|
||||
if (! $spotifyService->isConfigured()) {
|
||||
$this->error(__('music_productions.spotify_not_configured'));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->info(__('music_productions.spotify_sync_started'));
|
||||
|
||||
try {
|
||||
$albumSummaries = $spotifyService->getArtistAlbums();
|
||||
} catch (\Throwable $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (empty($albumSummaries)) {
|
||||
$this->warn(__('music_productions.spotify_no_albums'));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$updated = 0;
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
foreach ($albumSummaries as $index => $albumSummary) {
|
||||
$albumId = $albumSummary['id'] ?? null;
|
||||
|
||||
if (blank($albumId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$album = $spotifyService->getAlbum($albumId) ?? $albumSummary;
|
||||
$title = $album['name'] ?? __('music_productions.spotify_untitled_album');
|
||||
$artistName = collect($album['artists'] ?? [])->pluck('name')->first();
|
||||
$releaseDate = $this->parseReleaseDate($album['release_date'] ?? null, $album['release_date_precision'] ?? 'day');
|
||||
$spotifyUrl = $album['external_urls']['spotify'] ?? null;
|
||||
$coverUrl = collect($album['images'] ?? [])->first()['url'] ?? null;
|
||||
$albumType = $album['album_type'] ?? 'album';
|
||||
|
||||
$production = $this->findMatchingProduction($albumId, $title);
|
||||
|
||||
$action = $production ? 'update' : 'create';
|
||||
$this->line(sprintf(
|
||||
'[%d/%d] %s: %s (%s)',
|
||||
$index + 1,
|
||||
count($albumSummaries),
|
||||
$action === 'create' ? 'Yeni' : 'Güncelle',
|
||||
$title,
|
||||
$releaseDate?->format('Y-m-d') ?? '-'
|
||||
));
|
||||
|
||||
if ($dryRun) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'spotify_album_id' => $albumId,
|
||||
'spotify_url' => $spotifyUrl,
|
||||
'spotify_cover_url' => $coverUrl,
|
||||
'spotify_type' => $albumType,
|
||||
'spotify_synced_at' => now(),
|
||||
];
|
||||
|
||||
if (! $production) {
|
||||
$attributes['title'] = $title;
|
||||
$attributes['slug'] = SpotifyService::uniqueSlug($title);
|
||||
$attributes['client_name'] = $artistName;
|
||||
$attributes['production_date'] = $releaseDate;
|
||||
$attributes['content'] = $spotifyService->buildDefaultContent($album);
|
||||
$attributes['is_active'] = true;
|
||||
$attributes['sort_order'] = 0;
|
||||
|
||||
$coverPath = $spotifyService->downloadCoverImage($coverUrl, $albumId);
|
||||
if ($coverPath) {
|
||||
$attributes['cover_image'] = $coverPath;
|
||||
}
|
||||
|
||||
MusicProduction::create($attributes);
|
||||
$created++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blank($production->title)) {
|
||||
$attributes['title'] = $title;
|
||||
}
|
||||
|
||||
if (blank($production->client_name) && filled($artistName)) {
|
||||
$attributes['client_name'] = $artistName;
|
||||
}
|
||||
|
||||
if (blank($production->production_date) && $releaseDate) {
|
||||
$attributes['production_date'] = $releaseDate;
|
||||
}
|
||||
|
||||
if (blank($production->content)) {
|
||||
$attributes['content'] = $spotifyService->buildDefaultContent($album);
|
||||
}
|
||||
|
||||
if (blank($production->cover_image) && filled($coverUrl)) {
|
||||
$coverPath = $spotifyService->downloadCoverImage($coverUrl, $albumId);
|
||||
if ($coverPath) {
|
||||
$attributes['cover_image'] = $coverPath;
|
||||
}
|
||||
}
|
||||
|
||||
$production->update($attributes);
|
||||
$updated++;
|
||||
|
||||
usleep(150000);
|
||||
}
|
||||
|
||||
$message = __('music_productions.spotify_sync_completed', [
|
||||
'created' => $created,
|
||||
'updated' => $updated,
|
||||
'total' => count($albumSummaries),
|
||||
]);
|
||||
|
||||
$this->info($message);
|
||||
Log::info('Spotify music productions sync completed', [
|
||||
'created' => $created,
|
||||
'updated' => $updated,
|
||||
'total' => count($albumSummaries),
|
||||
'dry_run' => $dryRun,
|
||||
]);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function findMatchingProduction(string $albumId, string $title): ?MusicProduction
|
||||
{
|
||||
$bySpotifyId = MusicProduction::withTrashed()
|
||||
->where('spotify_album_id', $albumId)
|
||||
->first();
|
||||
|
||||
if ($bySpotifyId) {
|
||||
return $bySpotifyId->trashed() ? null : $bySpotifyId;
|
||||
}
|
||||
|
||||
$slug = Str::slug($title);
|
||||
|
||||
$bySlug = MusicProduction::query()
|
||||
->where('slug', $slug)
|
||||
->first();
|
||||
|
||||
if ($bySlug) {
|
||||
return $bySlug;
|
||||
}
|
||||
|
||||
return MusicProduction::query()
|
||||
->whereRaw('LOWER(title) = ?', [Str::lower($title)], 'and')
|
||||
->first();
|
||||
}
|
||||
|
||||
protected function parseReleaseDate(?string $date, string $precision = 'day'): ?Carbon
|
||||
{
|
||||
if (blank($date)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($precision) {
|
||||
'year' => Carbon::createFromFormat('Y', $date)->startOfYear(),
|
||||
'month' => Carbon::createFromFormat('Y-m', $date)->startOfMonth(),
|
||||
default => Carbon::parse($date),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\MusicProduction;
|
||||
use App\Services\YouTubeService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SyncYoutubeReleases extends Command
|
||||
{
|
||||
protected $signature = 'youtube:sync-releases
|
||||
{--channel= : Override YouTube main channel ID}
|
||||
{--topic= : Override YouTube Topic channel ID (Yayınlananlar / art track)}
|
||||
{--playlist= : Override Releases playlist ID}
|
||||
{--dry-run : Show changes without writing to database}';
|
||||
|
||||
protected $description = 'YouTube kanalının Releases (Yayınlar) listesindeki art track kayıtlarını müzik prodüksiyonlarına senkronize eder';
|
||||
|
||||
public function handle(YouTubeService $youTubeService): int
|
||||
{
|
||||
if ($this->option('channel') || $this->option('topic') || $this->option('playlist')) {
|
||||
$youTubeService = new YouTubeService(
|
||||
channelId: $this->option('channel') ?? config('services.youtube.channel_id'),
|
||||
topicChannelId: $this->option('topic') ?? config('services.youtube.topic_channel_id'),
|
||||
playlistId: $this->option('playlist') ?? config('services.youtube.playlist_id'),
|
||||
);
|
||||
}
|
||||
|
||||
if (! $youTubeService->isConfigured()) {
|
||||
$this->error(__('music_productions.youtube_not_configured'));
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$this->info(__('music_productions.youtube_sync_started'));
|
||||
$this->line(__('music_productions.youtube_sync_source', [
|
||||
'source' => $youTubeService->getReleasesSourceLabel(),
|
||||
]));
|
||||
$this->line('Playlist ID: ' . $youTubeService->resolveReleasesPlaylistId());
|
||||
|
||||
try {
|
||||
$releases = $youTubeService->getReleases();
|
||||
} catch (\Throwable $exception) {
|
||||
$this->error($exception->getMessage());
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (empty($releases)) {
|
||||
$this->warn(__('music_productions.youtube_no_releases'));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$created = 0;
|
||||
$updated = 0;
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
|
||||
foreach ($releases as $index => $release) {
|
||||
$videoId = $release['video_id'] ?? null;
|
||||
|
||||
if (blank($videoId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = $release['title'] ?? __('music_productions.youtube_untitled_release');
|
||||
$releaseDate = filled($release['release_date'] ?? null)
|
||||
? Carbon::parse($release['release_date'])
|
||||
: null;
|
||||
$youtubeUrl = $release['youtube_url'] ?? null;
|
||||
$coverUrl = $release['cover_url'] ?? null;
|
||||
$description = $release['description'] ?? '';
|
||||
|
||||
$production = $this->findMatchingProduction($videoId, $title);
|
||||
|
||||
$action = $production ? 'update' : 'create';
|
||||
$this->line(sprintf(
|
||||
'[%d/%d] %s: %s (%s)',
|
||||
$index + 1,
|
||||
count($releases),
|
||||
$action === 'create' ? 'Yeni' : 'Güncelle',
|
||||
$title,
|
||||
$releaseDate?->format('Y-m-d') ?? '-'
|
||||
));
|
||||
|
||||
if ($dryRun) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes = [
|
||||
'youtube_video_id' => $videoId,
|
||||
'youtube_url' => $youtubeUrl,
|
||||
'youtube_cover_url' => $coverUrl,
|
||||
'youtube_description' => $description,
|
||||
'youtube_synced_at' => now(),
|
||||
];
|
||||
|
||||
if (! $production) {
|
||||
$attributes['title'] = $title;
|
||||
$attributes['slug'] = YouTubeService::uniqueSlug($title);
|
||||
$attributes['production_date'] = $releaseDate;
|
||||
$attributes['content'] = $youTubeService->buildDefaultContent($release);
|
||||
$attributes['is_active'] = true;
|
||||
$attributes['sort_order'] = 0;
|
||||
|
||||
$coverPath = $youTubeService->downloadCoverImage($coverUrl, $videoId);
|
||||
if ($coverPath) {
|
||||
$attributes['cover_image'] = $coverPath;
|
||||
}
|
||||
|
||||
MusicProduction::create($attributes);
|
||||
$created++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blank($production->title)) {
|
||||
$attributes['title'] = $title;
|
||||
}
|
||||
|
||||
if ($releaseDate) {
|
||||
$attributes['production_date'] = $releaseDate;
|
||||
}
|
||||
|
||||
if (blank($production->content)) {
|
||||
$attributes['content'] = $youTubeService->buildDefaultContent($release);
|
||||
}
|
||||
|
||||
if (blank($production->cover_image) && filled($coverUrl)) {
|
||||
$coverPath = $youTubeService->downloadCoverImage($coverUrl, $videoId);
|
||||
if ($coverPath) {
|
||||
$attributes['cover_image'] = $coverPath;
|
||||
}
|
||||
}
|
||||
|
||||
$production->update($attributes);
|
||||
$updated++;
|
||||
|
||||
usleep(150000);
|
||||
}
|
||||
|
||||
$message = __('music_productions.youtube_sync_completed', [
|
||||
'created' => $created,
|
||||
'updated' => $updated,
|
||||
'total' => count($releases),
|
||||
]);
|
||||
|
||||
$this->info($message);
|
||||
Log::info('YouTube releases sync completed', [
|
||||
'created' => $created,
|
||||
'updated' => $updated,
|
||||
'total' => count($releases),
|
||||
'dry_run' => $dryRun,
|
||||
]);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function findMatchingProduction(string $videoId, string $title): ?MusicProduction
|
||||
{
|
||||
$byYoutubeId = MusicProduction::withTrashed()
|
||||
->where('youtube_video_id', $videoId)
|
||||
->first();
|
||||
|
||||
if ($byYoutubeId) {
|
||||
return $byYoutubeId->trashed() ? null : $byYoutubeId;
|
||||
}
|
||||
|
||||
$slug = Str::slug($title);
|
||||
|
||||
$bySlug = MusicProduction::query()
|
||||
->whereNull('youtube_video_id', 'and', false)
|
||||
->where('slug', '=', $slug)
|
||||
->first();
|
||||
|
||||
if ($bySlug) {
|
||||
return $bySlug;
|
||||
}
|
||||
|
||||
return MusicProduction::query()
|
||||
->whereNull('youtube_video_id', 'and', false)
|
||||
->whereRaw('LOWER(title) = ?', [Str::lower($title)], 'and')
|
||||
->first();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Awards;
|
||||
|
||||
use App\Filament\Admin\Resources\Awards\Pages\CreateAward;
|
||||
use App\Filament\Admin\Resources\Awards\Pages\EditAward;
|
||||
use App\Filament\Admin\Resources\Awards\Pages\ListAwards;
|
||||
use App\Filament\Admin\Resources\Awards\Schemas\AwardForm;
|
||||
use App\Filament\Admin\Resources\Awards\Tables\AwardsTable;
|
||||
use App\Models\Award;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class AwardResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Award::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-trophy';
|
||||
|
||||
protected static ?int $navigationSort = 46;
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('awards.navigation_group');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('awards.navigation_label');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('awards.model_label');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('awards.plural_model_label');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return AwardForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return AwardsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListAwards::route('/'),
|
||||
'create' => CreateAward::route('/create'),
|
||||
'edit' => EditAward::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRecordRouteBindingEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getRecordRouteBindingEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Awards\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Awards\AwardResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateAward extends CreateRecord
|
||||
{
|
||||
protected static string $resource = AwardResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('awards.create') ?? 'Yeni Ödül Ekle';
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
protected function getCreatedNotificationTitle(): ?string
|
||||
{
|
||||
return __('awards.created_successfully') ?? 'Ödül başarıyla eklendi.';
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Awards\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Awards\AwardResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditAward extends EditRecord
|
||||
{
|
||||
protected static string $resource = AwardResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('awards.edit') ?? 'Ödülü Düzenle';
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->label(__('awards.delete') ?? 'Sil'),
|
||||
ForceDeleteAction::make()
|
||||
->label(__('awards.force_delete') ?? 'Kalıcı Olarak Sil'),
|
||||
RestoreAction::make()
|
||||
->label(__('awards.restore') ?? 'Geri Yükle'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
protected function getSavedNotificationTitle(): ?string
|
||||
{
|
||||
return __('awards.updated_successfully') ?? 'Ödül başarıyla güncellendi.';
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
return array_merge($data, TranslationTabs::fillFromRecord($this->record));
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Awards\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Awards\AwardResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListAwards extends ListRecords
|
||||
{
|
||||
protected static string $resource = AwardResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('awards.title') ?? 'Ödüllerimiz';
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label(__('awards.create') ?? 'Yeni Ödül Ekle'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Awards\Schemas;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class AwardForm
|
||||
{
|
||||
public static function categoryOptions(): array
|
||||
{
|
||||
return [
|
||||
'hackathon' => __('awards.category_hackathon') ?? 'Hackathon',
|
||||
'export' => __('awards.category_export') ?? 'İhracat',
|
||||
'innovation' => __('awards.category_innovation') ?? 'İnovasyon',
|
||||
'design' => __('awards.category_design') ?? 'Tasarım',
|
||||
'general' => __('awards.category_general') ?? 'Genel',
|
||||
];
|
||||
}
|
||||
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->columns(3)
|
||||
->schema([
|
||||
Section::make(__('awards.content_section') ?? 'Ödül İçeriği')
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label(__('awards.title_field') ?? 'Ödül Başlığı (Varsayılan)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('issuer')
|
||||
->label(__('awards.issuer_field') ?? 'Ödülü Veren Kurum (Varsayılan)')
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
Textarea::make('description')
|
||||
->label(__('awards.description_field') ?? 'Açıklama (Varsayılan)')
|
||||
->required()
|
||||
->rows(5)
|
||||
->columnSpanFull(),
|
||||
|
||||
TranslationTabs::make([
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('awards.title_field') ?? 'Ödül Başlığı',
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'issuer' => [
|
||||
'type' => 'text',
|
||||
'label' => __('awards.issuer_field') ?? 'Ödülü Veren Kurum',
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'description' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('awards.description_field') ?? 'Açıklama',
|
||||
'required' => false,
|
||||
'rows' => 5,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->columnSpan(2),
|
||||
|
||||
Section::make(__('awards.settings_section') ?? 'Ödül Ayarları')
|
||||
->schema([
|
||||
FileUpload::make('image')
|
||||
->label(__('awards.image_field') ?? 'Ödül Görseli / Logo')
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('awards')
|
||||
->required(),
|
||||
|
||||
DatePicker::make('award_date')
|
||||
->label(__('awards.date_field') ?? 'Ödül Tarihi')
|
||||
->required(),
|
||||
|
||||
Select::make('category')
|
||||
->label(__('awards.category_field') ?? 'Kategori')
|
||||
->options(self::categoryOptions())
|
||||
->default('general')
|
||||
->required()
|
||||
->native(false),
|
||||
|
||||
TextInput::make('external_link')
|
||||
->label(__('awards.link_field') ?? 'Doğrulama / Haber Linki')
|
||||
->url()
|
||||
->maxLength(255),
|
||||
|
||||
TextInput::make('sort_order')
|
||||
->label(__('awards.sort_order_field') ?? 'Sıralama')
|
||||
->numeric()
|
||||
->default(0)
|
||||
->minValue(0),
|
||||
|
||||
Toggle::make('is_featured')
|
||||
->label(__('awards.is_featured_field') ?? 'Öne Çıkarılan Ödül')
|
||||
->default(false),
|
||||
|
||||
Toggle::make('is_active')
|
||||
->label(__('awards.is_active_field') ?? 'Aktif / Görünür')
|
||||
->default(true),
|
||||
])
|
||||
->columnSpan(1),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Awards\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ToggleColumn;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class AwardsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
ImageColumn::make('image')
|
||||
->label(__('awards.table_image') ?? 'Logo')
|
||||
->disk('public')
|
||||
->square()
|
||||
->size(50),
|
||||
|
||||
TextColumn::make('title')
|
||||
->label(__('awards.table_title') ?? 'Ödül Başlığı')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(40),
|
||||
|
||||
TextColumn::make('issuer')
|
||||
->label(__('awards.table_issuer') ?? 'Veren Kurum')
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(30),
|
||||
|
||||
TextColumn::make('award_date')
|
||||
->label(__('awards.table_date') ?? 'Ödül Tarihi')
|
||||
->date('d.m.Y')
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('category')
|
||||
->label(__('awards.table_category') ?? 'Kategori')
|
||||
->badge()
|
||||
->formatStateUsing(fn (string $state): string => match ($state) {
|
||||
'hackathon' => __('awards.category_hackathon') ?? 'Hackathon',
|
||||
'export' => __('awards.category_export') ?? 'İhracat',
|
||||
'innovation' => __('awards.category_innovation') ?? 'İnovasyon',
|
||||
'design' => __('awards.category_design') ?? 'Tasarım',
|
||||
default => __('awards.category_general') ?? 'Genel',
|
||||
})
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
|
||||
ToggleColumn::make('is_featured')
|
||||
->label(__('awards.table_is_featured') ?? 'Öne Çıkan')
|
||||
->alignCenter(),
|
||||
|
||||
ToggleColumn::make('is_active')
|
||||
->label(__('awards.table_is_active') ?? 'Aktif')
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('sort_order')
|
||||
->label(__('awards.sort_order_field') ?? 'Sıra')
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
])
|
||||
->filters([
|
||||
TernaryFilter::make('is_active')
|
||||
->label(__('awards.is_active_field') ?? 'Aktiflik Durumu'),
|
||||
TernaryFilter::make('is_featured')
|
||||
->label(__('awards.is_featured_field') ?? 'Öne Çıkarılma Durumu'),
|
||||
TrashedFilter::make(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->label(__('awards.edit') ?? 'Düzenle'),
|
||||
])
|
||||
->actions([
|
||||
// Individual actions can be put here if necessary
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->label(__('awards.delete') ?? 'Sil'),
|
||||
RestoreBulkAction::make()
|
||||
->label(__('awards.restore') ?? 'Geri Yükle'),
|
||||
ForceDeleteBulkAction::make()
|
||||
->label(__('awards.force_delete') ?? 'Kalıcı Olarak Sil'),
|
||||
]),
|
||||
])
|
||||
->defaultSort('sort_order', 'asc')
|
||||
->reorderable('sort_order');
|
||||
}
|
||||
}
|
||||
+181
-36
@@ -6,7 +6,16 @@ use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Schemas\Components\Utilities\Set;
|
||||
use Illuminate\Support\Str;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Components\Livewire;
|
||||
|
||||
class CareerApplicationForm
|
||||
{
|
||||
@@ -14,42 +23,178 @@ class CareerApplicationForm
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
TextInput::make('name')
|
||||
->label(__('career.name'))
|
||||
->required()
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('email')
|
||||
->label(__('career.email'))
|
||||
->email()
|
||||
->required()
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('phone')
|
||||
->label(__('career.phone'))
|
||||
->disabled(),
|
||||
|
||||
FileUpload::make('cv_path')
|
||||
->label(__('career.cv'))
|
||||
->disk('public')
|
||||
->directory('cvs')
|
||||
->required()
|
||||
->disabled(),
|
||||
|
||||
Textarea::make('message')
|
||||
->label(__('career.message'))
|
||||
->disabled()
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('status')
|
||||
->label(__('career.status'))
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'reviewed' => 'Reviewed',
|
||||
'rejected' => 'Rejected',
|
||||
'accepted' => 'Accepted',
|
||||
])
|
||||
->required(),
|
||||
Tabs::make('Tabs')
|
||||
->tabs([
|
||||
Tab::make('Kişisel ve Başvuru Bilgileri')
|
||||
->icon('heroicon-m-user')
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label(__('career.name'))
|
||||
->required()
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('email')
|
||||
->label(__('career.email'))
|
||||
->email()
|
||||
->required()
|
||||
->disabled(),
|
||||
|
||||
TextInput::make('phone')
|
||||
->label(__('career.phone'))
|
||||
->disabled(),
|
||||
|
||||
Select::make('status')
|
||||
->label(__('career.status'))
|
||||
->options([
|
||||
'pending' => __('career.pending'),
|
||||
'reviewed' => __('career.reviewed'),
|
||||
'rejected' => __('career.rejected'),
|
||||
'accepted' => __('career.accepted'),
|
||||
'waiting_document' => __('career.waiting_document'),
|
||||
])
|
||||
->required(),
|
||||
|
||||
Textarea::make('message')
|
||||
->label(__('career.message'))
|
||||
->disabled()
|
||||
->columnSpanFull(),
|
||||
])->columns(2),
|
||||
|
||||
Tab::make('Staj Belgeleri & Giriş Bilgileri')
|
||||
->icon('heroicon-m-document-text')
|
||||
->schema([
|
||||
FileUpload::make('cv_path')
|
||||
->label(__('career.cv'))
|
||||
->disk('public')
|
||||
->directory('cvs')
|
||||
->required()
|
||||
->disabled()
|
||||
->downloadable(),
|
||||
|
||||
TextInput::make('username')
|
||||
->label('Kullanıcı Adı')
|
||||
->unique(ignoreRecord: true)
|
||||
->nullable(),
|
||||
|
||||
TextInput::make('password')
|
||||
->label('Şifre')
|
||||
->password()
|
||||
->revealable()
|
||||
->dehydrateStateUsing(fn ($state) => filled($state) ? Hash::make($state) : null)
|
||||
->dehydrated(fn ($state) => filled($state))
|
||||
->placeholder('Şifreyi değiştirmek istemiyorsanız boş bırakın')
|
||||
->nullable()
|
||||
->suffixAction(
|
||||
Action::make('generatePassword')
|
||||
->icon('heroicon-m-arrow-path')
|
||||
->action(fn (Set $set) => $set('password', Str::random(12)))
|
||||
),
|
||||
|
||||
FileUpload::make('to_be_signed_internship_form_path')
|
||||
->label('İmzalanacak Staj Formu (Stajyerden)')
|
||||
->disk('public')
|
||||
->directory('to_be_signed_interns')
|
||||
->downloadable()
|
||||
->nullable(),
|
||||
|
||||
FileUpload::make('signed_internship_form_path')
|
||||
->label('İmzalı Staj Formu')
|
||||
->disk('public')
|
||||
->directory('signed_interns')
|
||||
->downloadable()
|
||||
->nullable(),
|
||||
|
||||
DatePicker::make('internship_start_date')
|
||||
->label('Staj Başlangıç Tarihi')
|
||||
->live()
|
||||
->afterStateUpdated(function ($state, $get, Set $set) {
|
||||
if ($state && $get('internship_total_days')) {
|
||||
self::calculateEndDate($state, $get('internship_total_days'), $set);
|
||||
} elseif ($state && $get('internship_end_date')) {
|
||||
self::calculateTotalDays($state, $get('internship_end_date'), $set);
|
||||
}
|
||||
})
|
||||
->nullable(),
|
||||
|
||||
DatePicker::make('internship_end_date')
|
||||
->label('Staj Bitiş Tarihi')
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, $get, Set $set) => self::calculateTotalDays($get('internship_start_date'), $state, $set))
|
||||
->nullable(),
|
||||
|
||||
TextInput::make('internship_total_days')
|
||||
->label('Toplam Staj Süresi (İş Günü)')
|
||||
->numeric()
|
||||
->live()
|
||||
->afterStateUpdated(fn ($state, $get, Set $set) => self::calculateEndDate($get('internship_start_date'), $state, $set))
|
||||
->nullable(),
|
||||
])->columns(2),
|
||||
|
||||
Tab::make('Staj Günlüğü')
|
||||
->icon('heroicon-m-squares-plus')
|
||||
->schema([
|
||||
TextInput::make('github_repo')
|
||||
->label('GitHub Depo URL\'si')
|
||||
->url()
|
||||
->nullable()
|
||||
->live(),
|
||||
|
||||
Livewire::make(\App\Livewire\InternJournalTimeline::class)
|
||||
->columnSpanFull()
|
||||
])
|
||||
])->columnSpanFull()
|
||||
]);
|
||||
}
|
||||
|
||||
public static function calculateTotalDays($start, $end, Set $set): void
|
||||
{
|
||||
if (!$start || !$end) {
|
||||
$set('internship_total_days', null);
|
||||
return;
|
||||
}
|
||||
|
||||
$startDate = \Carbon\Carbon::parse($start);
|
||||
$endDate = \Carbon\Carbon::parse($end);
|
||||
|
||||
if ($startDate->gt($endDate)) {
|
||||
$set('internship_total_days', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
$days = 0;
|
||||
while ($startDate->lte($endDate)) {
|
||||
if (!$startDate->isWeekend()) {
|
||||
$days++;
|
||||
}
|
||||
$startDate->addDay();
|
||||
}
|
||||
|
||||
$set('internship_total_days', $days);
|
||||
}
|
||||
|
||||
public static function calculateEndDate($start, $totalDays, Set $set): void
|
||||
{
|
||||
if (!$start || !$totalDays || $totalDays <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$startDate = \Carbon\Carbon::parse($start);
|
||||
$daysToAdd = intval($totalDays);
|
||||
|
||||
$endDate = $startDate->copy();
|
||||
$count = 0;
|
||||
$temp = $startDate->copy();
|
||||
|
||||
while ($count < $daysToAdd) {
|
||||
if ($temp->isWeekend()) {
|
||||
$temp->addDay();
|
||||
continue;
|
||||
}
|
||||
$endDate = $temp->copy();
|
||||
$temp->addDay();
|
||||
$count++;
|
||||
}
|
||||
|
||||
$set('internship_end_date', $endDate->format('Y-m-d'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,10 @@ class CareerApplicationsTable
|
||||
'reviewed' => 'info',
|
||||
'rejected' => 'danger',
|
||||
'accepted' => 'success',
|
||||
'waiting_document' => 'warning',
|
||||
default => 'gray',
|
||||
}),
|
||||
})
|
||||
->formatStateUsing(fn (string $state): string => __("career.{$state}")),
|
||||
|
||||
TextColumn::make('git_knowledge')
|
||||
->label(__('career.git_knowledge'))
|
||||
@@ -73,10 +75,11 @@ class CareerApplicationsTable
|
||||
SelectFilter::make('status')
|
||||
->label(__('career.status'))
|
||||
->options([
|
||||
'pending' => 'Pending',
|
||||
'reviewed' => 'Reviewed',
|
||||
'rejected' => 'Rejected',
|
||||
'accepted' => 'Accepted',
|
||||
'pending' => __('career.pending'),
|
||||
'reviewed' => __('career.reviewed'),
|
||||
'rejected' => __('career.rejected'),
|
||||
'accepted' => __('career.accepted'),
|
||||
'waiting_document' => __('career.waiting_document'),
|
||||
]),
|
||||
SelectFilter::make('type')
|
||||
->label(__('career.type'))
|
||||
@@ -91,6 +94,12 @@ class CareerApplicationsTable
|
||||
->icon('heroicon-o-arrow-down-tray')
|
||||
->url(fn ($record) => Storage::disk('public')->url($record->cv_path))
|
||||
->openUrlInNewTab(),
|
||||
Action::make('download_signed_form')
|
||||
->label('İmzalı Form İndir')
|
||||
->icon('heroicon-o-document-check')
|
||||
->url(fn ($record) => $record->signed_internship_form_path ? Storage::disk('public')->url($record->signed_internship_form_path) : null)
|
||||
->visible(fn ($record) => !empty($record->signed_internship_form_path))
|
||||
->openUrlInNewTab(),
|
||||
Action::make('download_nda')
|
||||
->label(__('career.nda'))
|
||||
->icon('heroicon-o-shield-check')
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\CompanyHistoryItems;
|
||||
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\Pages\CreateCompanyHistoryItem;
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\Pages\EditCompanyHistoryItem;
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\Pages\ListCompanyHistoryItems;
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\Schemas\CompanyHistoryItemForm;
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\Tables\CompanyHistoryItemsTable;
|
||||
use App\Models\CompanyHistoryItem;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class CompanyHistoryItemResource extends Resource
|
||||
{
|
||||
protected static ?string $model = CompanyHistoryItem::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-clock';
|
||||
|
||||
protected static ?int $navigationSort = 45;
|
||||
|
||||
public static function getNavigationGroup(): ?string
|
||||
{
|
||||
return __('company_history.navigation_group');
|
||||
}
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('company_history.navigation_label');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('company_history.model_label');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('company_history.plural_model_label');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return CompanyHistoryItemForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return CompanyHistoryItemsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListCompanyHistoryItems::route('/'),
|
||||
'create' => CreateCompanyHistoryItem::route('/create'),
|
||||
'edit' => EditCompanyHistoryItem::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRecordRouteBindingEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getRecordRouteBindingEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\CompanyHistoryItems\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\CompanyHistoryItemResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateCompanyHistoryItem extends CreateRecord
|
||||
{
|
||||
protected static string $resource = CompanyHistoryItemResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('company_history.create');
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
protected function getCreatedNotificationTitle(): ?string
|
||||
{
|
||||
return __('company_history.created_successfully');
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeCreate(array $data): array
|
||||
{
|
||||
if (empty($data['position'])) {
|
||||
$data['position'] = null;
|
||||
}
|
||||
|
||||
unset($data['color_custom']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\CompanyHistoryItems\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\CompanyHistoryItemResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditCompanyHistoryItem extends EditRecord
|
||||
{
|
||||
protected static string $resource = CompanyHistoryItemResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('company_history.edit');
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
DeleteAction::make()
|
||||
->label(__('company_history.delete')),
|
||||
ForceDeleteAction::make()
|
||||
->label(__('company_history.force_delete')),
|
||||
RestoreAction::make()
|
||||
->label(__('company_history.restore')),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
protected function getSavedNotificationTitle(): ?string
|
||||
{
|
||||
return __('company_history.updated_successfully');
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
return array_merge($data, TranslationTabs::fillFromRecord($this->record));
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeSave(array $data): array
|
||||
{
|
||||
if (empty($data['position'])) {
|
||||
$data['position'] = null;
|
||||
}
|
||||
|
||||
unset($data['color_custom']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\CompanyHistoryItems\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\CompanyHistoryItems\CompanyHistoryItemResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListCompanyHistoryItems extends ListRecords
|
||||
{
|
||||
protected static string $resource = CompanyHistoryItemResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('company_history.title');
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label(__('company_history.create')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\CompanyHistoryItems\Schemas;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class CompanyHistoryItemForm
|
||||
{
|
||||
public static function iconOptions(): array
|
||||
{
|
||||
return [
|
||||
'heroicon-o-building-office-2' => __('company_history.icon_building'),
|
||||
'heroicon-o-academic-cap' => __('company_history.icon_academic'),
|
||||
'heroicon-o-globe-alt' => __('company_history.icon_globe'),
|
||||
'heroicon-o-users' => __('company_history.icon_users'),
|
||||
'heroicon-o-trophy' => __('company_history.icon_trophy'),
|
||||
'heroicon-o-map' => __('company_history.icon_map'),
|
||||
'heroicon-o-light-bulb' => __('company_history.icon_light_bulb'),
|
||||
'heroicon-o-rocket-launch' => __('company_history.icon_rocket'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function quarterOptions(): array
|
||||
{
|
||||
return [
|
||||
'Q1' => __('company_history.quarter_q1'),
|
||||
'Q2' => __('company_history.quarter_q2'),
|
||||
'Q3' => __('company_history.quarter_q3'),
|
||||
'Q4' => __('company_history.quarter_q4'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function colorPresets(): array
|
||||
{
|
||||
return [
|
||||
'#17a2b8' => __('company_history.color_teal'),
|
||||
'#f5a623' => __('company_history.color_orange'),
|
||||
'#e91e63' => __('company_history.color_pink'),
|
||||
'#2c3e50' => __('company_history.color_navy'),
|
||||
'#5dade2' => __('company_history.color_blue'),
|
||||
'#27ae60' => __('company_history.color_green'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->columns(3)
|
||||
->schema([
|
||||
Section::make(__('company_history.content_section'))
|
||||
->schema([
|
||||
TextInput::make('year')
|
||||
->label(__('company_history.year_field'))
|
||||
->required()
|
||||
->numeric()
|
||||
->minValue(1900)
|
||||
->maxValue(2100),
|
||||
|
||||
Select::make('quarter')
|
||||
->label(__('company_history.quarter_field'))
|
||||
->options(self::quarterOptions())
|
||||
->default('Q1')
|
||||
->required()
|
||||
->native(false),
|
||||
|
||||
TextInput::make('title')
|
||||
->label(__('company_history.title_field'))
|
||||
->required()
|
||||
->maxLength(255),
|
||||
|
||||
Textarea::make('content')
|
||||
->label(__('company_history.content_field'))
|
||||
->required()
|
||||
->rows(5)
|
||||
->columnSpanFull(),
|
||||
|
||||
TranslationTabs::make([
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('company_history.title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'content' => [
|
||||
'type' => 'textarea',
|
||||
'label' => __('company_history.content_field'),
|
||||
'required' => false,
|
||||
'rows' => 5,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->columnSpan(2)
|
||||
->collapsible(false),
|
||||
|
||||
Section::make(__('company_history.settings_section'))
|
||||
->schema([
|
||||
Select::make('color')
|
||||
->label(__('company_history.color_field'))
|
||||
->options(self::colorPresets())
|
||||
->default('#17a2b8')
|
||||
->required()
|
||||
->native(false)
|
||||
->searchable(),
|
||||
|
||||
Select::make('icon')
|
||||
->label(__('company_history.icon_field'))
|
||||
->options(self::iconOptions())
|
||||
->default('heroicon-o-building-office-2')
|
||||
->required()
|
||||
->native(false)
|
||||
->searchable(),
|
||||
|
||||
Select::make('position')
|
||||
->label(__('company_history.position_field'))
|
||||
->options([
|
||||
'' => __('company_history.position_auto'),
|
||||
'left' => __('company_history.position_left'),
|
||||
'right' => __('company_history.position_right'),
|
||||
])
|
||||
->default(null),
|
||||
|
||||
TextInput::make('sort_order')
|
||||
->label(__('company_history.sort_order_field'))
|
||||
->numeric()
|
||||
->default(0)
|
||||
->minValue(0),
|
||||
|
||||
Checkbox::make('is_active')
|
||||
->label(__('company_history.is_active_field'))
|
||||
->default(true),
|
||||
])
|
||||
->columnSpan(1),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\CompanyHistoryItems\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\ColorColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ToggleColumn;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Filters\TrashedFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class CompanyHistoryItemsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('year')
|
||||
->label(__('company_history.table_year'))
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('quarter')
|
||||
->label(__('company_history.quarter_field'))
|
||||
->badge()
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
|
||||
ColorColumn::make('color')
|
||||
->label(__('company_history.color_field')),
|
||||
|
||||
TextColumn::make('title')
|
||||
->label(__('company_history.table_title'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(40),
|
||||
|
||||
TextColumn::make('content')
|
||||
->label(__('company_history.content_field'))
|
||||
->limit(60)
|
||||
->toggleable(),
|
||||
|
||||
TextColumn::make('position')
|
||||
->label(__('company_history.position_field'))
|
||||
->formatStateUsing(fn (?string $state): string => match ($state) {
|
||||
'left' => __('company_history.position_left'),
|
||||
'right' => __('company_history.position_right'),
|
||||
default => __('company_history.position_auto'),
|
||||
})
|
||||
->toggleable(),
|
||||
|
||||
ToggleColumn::make('is_active')
|
||||
->label(__('company_history.table_is_active'))
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('sort_order')
|
||||
->label(__('company_history.sort_order_field'))
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('company_history.table_created_at'))
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('company_history.table_updated_at'))
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
TernaryFilter::make('is_active')
|
||||
->label(__('company_history.is_active_field')),
|
||||
TrashedFilter::make(),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->label(__('company_history.edit')),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->label(__('company_history.delete')),
|
||||
RestoreBulkAction::make()
|
||||
->label(__('company_history.restore')),
|
||||
ForceDeleteBulkAction::make()
|
||||
->label(__('company_history.force_delete')),
|
||||
]),
|
||||
])
|
||||
->defaultSort('year', 'asc')
|
||||
->reorderable('sort_order');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MusicProductions;
|
||||
|
||||
use App\Filament\Admin\Resources\MusicProductions\Pages\CreateMusicProduction;
|
||||
use App\Filament\Admin\Resources\MusicProductions\Pages\EditMusicProduction;
|
||||
use App\Filament\Admin\Resources\MusicProductions\Pages\ListMusicProductions;
|
||||
use App\Filament\Admin\Resources\MusicProductions\Schemas\MusicProductionForm;
|
||||
use App\Filament\Admin\Resources\MusicProductions\Tables\MusicProductionsTable;
|
||||
use App\Models\MusicProduction;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class MusicProductionResource extends Resource
|
||||
{
|
||||
protected static ?string $model = MusicProduction::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-musical-note';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('music_productions.title');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('music_productions.model_label');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('music_productions.plural_model_label');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return MusicProductionForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return MusicProductionsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListMusicProductions::route('/'),
|
||||
'create' => CreateMusicProduction::route('/create'),
|
||||
'edit' => EditMusicProduction::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRecordRouteBindingEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getRecordRouteBindingEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MusicProductions\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MusicProductions\MusicProductionResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
class CreateMusicProduction extends CreateRecord
|
||||
{
|
||||
protected static string $resource = MusicProductionResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('music_productions.create');
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
protected function getCreatedNotificationTitle(): ?string
|
||||
{
|
||||
return __('music_productions.created_successfully');
|
||||
}
|
||||
|
||||
protected function afterCreate(): void
|
||||
{
|
||||
// Save translations
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MusicProductions\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\MusicProductions\MusicProductionResource;
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\DeleteAction;
|
||||
use Filament\Actions\ForceDeleteAction;
|
||||
use Filament\Actions\RestoreAction;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
|
||||
class EditMusicProduction extends EditRecord
|
||||
{
|
||||
protected static string $resource = MusicProductionResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('music_productions.edit');
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('save')
|
||||
->label(__('music_productions.save'))
|
||||
->action('save')
|
||||
->keyBindings(['mod+s'])
|
||||
->color('primary')
|
||||
->size('sm'),
|
||||
Action::make('cancel')
|
||||
->label(__('music_productions.cancel'))
|
||||
->url($this->getResource()::getUrl('index'))
|
||||
->color('gray')
|
||||
->size('sm'),
|
||||
DeleteAction::make()
|
||||
->label(__('music_productions.delete'))
|
||||
->size('sm'),
|
||||
RestoreAction::make()
|
||||
->label(__('music_productions.restore'))
|
||||
->size('sm'),
|
||||
ForceDeleteAction::make()
|
||||
->label(__('music_productions.force_delete'))
|
||||
->size('sm'),
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return []; // Hide standard bottom save/cancel buttons
|
||||
}
|
||||
|
||||
protected function getRedirectUrl(): string
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
protected function getSavedNotificationTitle(): ?string
|
||||
{
|
||||
return __('music_productions.updated_successfully');
|
||||
}
|
||||
|
||||
protected function mutateFormDataBeforeFill(array $data): array
|
||||
{
|
||||
// Load existing translations
|
||||
$translationData = TranslationTabs::fillFromRecord($this->record);
|
||||
|
||||
\Log::info('Loading translations for music production edit', [
|
||||
'production_id' => $this->record->id,
|
||||
'translations' => $translationData
|
||||
]);
|
||||
|
||||
return array_merge($data, $translationData);
|
||||
}
|
||||
|
||||
protected function afterSave(): void
|
||||
{
|
||||
\Log::info('Saving music production translations', [
|
||||
'production_id' => $this->record->id,
|
||||
'form_state' => $this->form->getState()
|
||||
]);
|
||||
|
||||
// Save translations
|
||||
TranslationTabs::saveTranslations($this->record, $this->form->getState());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MusicProductions\Pages;
|
||||
|
||||
use App\Console\Commands\SyncSpotifyMusicProductions;
|
||||
use App\Console\Commands\SyncYoutubeReleases;
|
||||
use App\Filament\Admin\Resources\MusicProductions\MusicProductionResource;
|
||||
use App\Services\SpotifyService;
|
||||
use App\Services\YouTubeService;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class ListMusicProductions extends ListRecords
|
||||
{
|
||||
protected static string $resource = MusicProductionResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('music_productions.title');
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Action::make('syncSpotify')
|
||||
->label(__('music_productions.sync_spotify'))
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->color('success')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('music_productions.sync_spotify_heading'))
|
||||
->modalDescription(__('music_productions.sync_spotify_description'))
|
||||
->visible(fn (): bool => app(SpotifyService::class)->isConfigured())
|
||||
->action(function (): void {
|
||||
$exitCode = Artisan::call(SyncSpotifyMusicProductions::class);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
Notification::make()
|
||||
->title(__('music_productions.spotify_sync_failed'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(__('music_productions.spotify_sync_success'))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
Action::make('syncYoutube')
|
||||
->label(__('music_productions.sync_youtube'))
|
||||
->icon('heroicon-o-play')
|
||||
->color('danger')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('music_productions.sync_youtube_heading'))
|
||||
->modalDescription(__('music_productions.sync_youtube_description'))
|
||||
->visible(fn (): bool => app(YouTubeService::class)->isConfigured())
|
||||
->action(function (): void {
|
||||
$exitCode = Artisan::call(SyncYoutubeReleases::class);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
Notification::make()
|
||||
->title(__('music_productions.youtube_sync_failed'))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title(__('music_productions.youtube_sync_success'))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
CreateAction::make()
|
||||
->label(__('music_productions.create')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MusicProductions\Schemas;
|
||||
|
||||
use App\Filament\Admin\Resources\Components\TranslationTabs;
|
||||
use App\Models\MusicProduction;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\RichEditor;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Placeholder;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MusicProductionForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->columns(3)
|
||||
->schema([
|
||||
// Sol Kolon - Ana İçerik (2 sütun genişliğinde)
|
||||
Section::make(__('music_productions.content_section'))
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label(__('music_productions.title_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(function (string $operation, $state, callable $set) {
|
||||
if ($operation !== 'create') {
|
||||
return;
|
||||
}
|
||||
$set('slug', Str::slug($state));
|
||||
}),
|
||||
|
||||
TextInput::make('slug')
|
||||
->label(__('music_productions.slug_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->rules(['alpha_dash'])
|
||||
->helperText(__('music_productions.slug_helper')),
|
||||
|
||||
TextInput::make('client_name')
|
||||
->label(__('music_productions.client_name_field'))
|
||||
->maxLength(255),
|
||||
|
||||
RichEditor::make('content')
|
||||
->label(__('music_productions.content_field'))
|
||||
->fileAttachmentsDisk('public')
|
||||
->fileAttachmentsDirectory('music-productions')
|
||||
->fileAttachmentsVisibility('public')
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpan(2)
|
||||
->collapsible(false),
|
||||
|
||||
// Sağ Kolon - Ayarlar (1 sütun genişliğinde)
|
||||
Section::make(__('music_productions.settings_section'))
|
||||
->schema([
|
||||
FileUpload::make('cover_image')
|
||||
->label(__('music_productions.cover_image_field'))
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('music-productions/covers')
|
||||
->visibility('public')
|
||||
->imageEditor()
|
||||
->imageEditorAspectRatios([
|
||||
'16:9',
|
||||
'4:3',
|
||||
'1:1',
|
||||
])
|
||||
->helperText(__('music_productions.cover_image_helper'))
|
||||
->columnSpanFull(),
|
||||
|
||||
DatePicker::make('production_date')
|
||||
->label(__('music_productions.production_date_field'))
|
||||
->displayFormat('d.m.Y')
|
||||
->helperText(__('music_productions.production_date_helper'))
|
||||
->columnSpanFull(),
|
||||
|
||||
TextInput::make('sort_order')
|
||||
->label(__('music_productions.sort_order_field'))
|
||||
->numeric()
|
||||
->default(0)
|
||||
->columnSpanFull(),
|
||||
|
||||
Checkbox::make('is_active')
|
||||
->label(__('music_productions.is_active_field'))
|
||||
->default(true)
|
||||
->helperText(__('music_productions.is_active_helper'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpan(1)
|
||||
->collapsible(false),
|
||||
|
||||
Section::make(__('music_productions.spotify_section'))
|
||||
->schema([
|
||||
Placeholder::make('spotify_album_id_display')
|
||||
->label(__('music_productions.spotify_album_id_field'))
|
||||
->content(fn (?MusicProduction $record): string => $record?->spotify_album_id ?? '-'),
|
||||
|
||||
Placeholder::make('spotify_type_display')
|
||||
->label(__('music_productions.spotify_type_field'))
|
||||
->content(fn (?MusicProduction $record): string => $record?->spotify_type
|
||||
? __('music_productions.spotify_type_' . $record->spotify_type, [], $record->spotify_type)
|
||||
: '-'),
|
||||
|
||||
Placeholder::make('spotify_url_display')
|
||||
->label(__('music_productions.spotify_url_field'))
|
||||
->content(fn (?MusicProduction $record): string => $record?->spotify_url ?? '-'),
|
||||
|
||||
Placeholder::make('spotify_synced_at_display')
|
||||
->label(__('music_productions.spotify_synced_at_field'))
|
||||
->content(fn (?MusicProduction $record): string => $record?->spotify_synced_at
|
||||
?->timezone(config('app.timezone'))
|
||||
->format('d.m.Y H:i') ?? '-'),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true)
|
||||
->visible(fn (?MusicProduction $record): bool => filled($record?->spotify_album_id)),
|
||||
|
||||
Section::make(__('music_productions.youtube_section'))
|
||||
->schema([
|
||||
Placeholder::make('youtube_video_id_display')
|
||||
->label(__('music_productions.youtube_video_id_field'))
|
||||
->content(fn (?MusicProduction $record): string => $record?->youtube_video_id ?? '-'),
|
||||
|
||||
Placeholder::make('youtube_url_display')
|
||||
->label(__('music_productions.youtube_url_field'))
|
||||
->content(fn (?MusicProduction $record): string => $record?->youtube_url ?? '-'),
|
||||
|
||||
Placeholder::make('youtube_description_display')
|
||||
->label(__('music_productions.youtube_description_field'))
|
||||
->content(fn (?MusicProduction $record): string => filled($record?->youtube_description)
|
||||
? \Illuminate\Support\Str::limit($record->youtube_description, 500)
|
||||
: '-'),
|
||||
|
||||
Placeholder::make('youtube_synced_at_display')
|
||||
->label(__('music_productions.youtube_synced_at_field'))
|
||||
->content(fn (?MusicProduction $record): string => $record?->youtube_synced_at
|
||||
?->timezone(config('app.timezone'))
|
||||
->format('d.m.Y H:i') ?? '-'),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(true)
|
||||
->visible(fn (?MusicProduction $record): bool => filled($record?->youtube_video_id)),
|
||||
|
||||
// Alt Kısım - Galeri Görselleri
|
||||
Section::make(__('music_productions.gallery_field'))
|
||||
->schema([
|
||||
FileUpload::make('gallery')
|
||||
->label(__('music_productions.gallery_field'))
|
||||
->multiple()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('music-productions/gallery')
|
||||
->visibility('public')
|
||||
->helperText(__('music_productions.gallery_helper'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(false),
|
||||
|
||||
// Çeviriler Kısımı (Çoklu Dil)
|
||||
Section::make('🌍 ' . __('music_productions.translations_section'))
|
||||
->schema([
|
||||
TranslationTabs::make([
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('music_productions.title_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'client_name' => [
|
||||
'type' => 'text',
|
||||
'label' => __('music_productions.client_name_field'),
|
||||
'required' => false,
|
||||
'maxLength' => 255,
|
||||
],
|
||||
'content' => [
|
||||
'type' => 'richtext',
|
||||
'label' => __('music_productions.content_field'),
|
||||
'required' => false,
|
||||
],
|
||||
]),
|
||||
])
|
||||
->columnSpanFull()
|
||||
->collapsible(true)
|
||||
->collapsed(false),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\MusicProductions\Tables;
|
||||
|
||||
use App\Models\MusicProduction;
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ForceDeleteBulkAction;
|
||||
use Filament\Actions\RestoreBulkAction;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ToggleColumn;
|
||||
use Filament\Tables\Filters\TernaryFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class MusicProductionsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
ImageColumn::make('cover_image')
|
||||
->label(__('music_productions.cover_image_field'))
|
||||
->getStateUsing(fn (MusicProduction $record): ?string => $record->cover_image_url)
|
||||
->square()
|
||||
->size(60),
|
||||
|
||||
TextColumn::make('title')
|
||||
->label(__('music_productions.table_title'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(50),
|
||||
|
||||
TextColumn::make('slug')
|
||||
->label(__('music_productions.table_slug'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(30),
|
||||
|
||||
TextColumn::make('client_name')
|
||||
->label(__('music_productions.table_client_name'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(30),
|
||||
|
||||
TextColumn::make('production_date')
|
||||
->label(__('music_productions.table_production_date'))
|
||||
->date('d.m.Y')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('spotify_type')
|
||||
->label(__('music_productions.spotify_type_field'))
|
||||
->badge()
|
||||
->formatStateUsing(fn (?string $state): string => filled($state)
|
||||
? __('music_productions.spotify_type_' . $state, [], $state)
|
||||
: '-')
|
||||
->color(fn (?string $state): string => match ($state) {
|
||||
'single' => 'info',
|
||||
'album' => 'success',
|
||||
default => 'gray',
|
||||
})
|
||||
->toggleable(),
|
||||
|
||||
ToggleColumn::make('is_active')
|
||||
->label(__('music_productions.table_is_active'))
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('sort_order')
|
||||
->label(__('music_productions.sort_order_field'))
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('music_productions.table_created_at'))
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
|
||||
TextColumn::make('updated_at')
|
||||
->label(__('music_productions.table_updated_at'))
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
TernaryFilter::make('is_active')
|
||||
->label(__('music_productions.is_active_field')),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->label(__('music_productions.edit')),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->label(__('music_productions.delete')),
|
||||
RestoreBulkAction::make()
|
||||
->label(__('music_productions.restore')),
|
||||
ForceDeleteBulkAction::make()
|
||||
->label(__('music_productions.force_delete')),
|
||||
]),
|
||||
])
|
||||
->defaultSort('sort_order', 'asc');
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,14 @@ class BankAccountsSection
|
||||
'GBP' => 'Sterlin (£)',
|
||||
])
|
||||
->default('TRY')
|
||||
->live()
|
||||
->required(),
|
||||
|
||||
TextInput::make('swift_code')
|
||||
->label('SWIFT / BIC')
|
||||
->placeholder('Örn: TRHBTR2A')
|
||||
->required(fn (Get $get): bool => $get('currency') !== 'TRY'),
|
||||
|
||||
FileUpload::make('logo')
|
||||
->label('Banka Logosu')
|
||||
->image()
|
||||
|
||||
@@ -35,7 +35,6 @@ class ContentSection
|
||||
|
||||
RichEditor::make('content')
|
||||
->label(__('pages.content_field'))
|
||||
->required()
|
||||
->fileAttachmentsDisk('public')
|
||||
->fileAttachmentsDirectory('pages')
|
||||
->fileAttachmentsVisibility('public')
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
namespace App\Filament\Admin\Resources\Pages\Schemas\Sections;
|
||||
|
||||
use App\Support\PageTemplateHero;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\DateTimePicker;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Utilities\Get;
|
||||
|
||||
class PageSettingsSection
|
||||
{
|
||||
@@ -15,6 +17,13 @@ class PageSettingsSection
|
||||
{
|
||||
return Section::make(__('pages.form_section_page_settings'))
|
||||
->schema([
|
||||
Select::make('template')
|
||||
->label(__('pages.template_field'))
|
||||
->options(PageTemplateHero::templateFormOptions())
|
||||
->default('default')
|
||||
->live()
|
||||
->columnSpanFull(),
|
||||
|
||||
FileUpload::make('featured_image')
|
||||
->label(__('pages.featured_image_field'))
|
||||
->image()
|
||||
@@ -27,7 +36,13 @@ class PageSettingsSection
|
||||
'4:3',
|
||||
'1:1',
|
||||
])
|
||||
->helperText(__('pages.featured_image_helper_text'))
|
||||
->helperText(function (Get $get): string {
|
||||
if (PageTemplateHero::hasHero($get('template'))) {
|
||||
return __('pages.featured_image_template_hero_helper');
|
||||
}
|
||||
|
||||
return __('pages.featured_image_helper_text');
|
||||
})
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('author_id')
|
||||
@@ -61,25 +76,8 @@ class PageSettingsSection
|
||||
->preload()
|
||||
->helperText(__('pages.parent_helper_text'))
|
||||
->columnSpanFull(),
|
||||
|
||||
Select::make('template')
|
||||
->label(__('pages.template_field'))
|
||||
->options([
|
||||
'default' => __('pages.template_default'),
|
||||
'landing' => __('pages.template_landing'),
|
||||
'blog' => __('pages.template_blog'),
|
||||
'contact' => __('pages.template_contact'),
|
||||
'home' => 'Home',
|
||||
'corporate.testimonials' => 'Müşteri Görüşleri (Kurumsal)',
|
||||
'corporate.logos' => 'Logolarımız (Kurumsal)',
|
||||
'corporate.partners' => 'Çözüm Ortaklarımız (Kurumsal)',
|
||||
'corporate.bank-accounts' => 'Banka Bilgilerimiz (Kurumsal)',
|
||||
'corporate.online-payment' => 'Online Ödeme (Kurumsal)',
|
||||
])
|
||||
->default('default')
|
||||
->columnSpanFull(),
|
||||
|
||||
TextInput::make('sort_order')
|
||||
|
||||
TextInput::make('sort_order')
|
||||
->label(__('pages.sort_order_field'))
|
||||
->numeric()
|
||||
->default(0)
|
||||
|
||||
@@ -18,6 +18,12 @@ class PartnersSection
|
||||
Repeater::make('data.partners')
|
||||
->label('Partnerler')
|
||||
->schema([
|
||||
TextInput::make('name')
|
||||
->label('Partner Adı')
|
||||
->placeholder('Örn: Microsoft')
|
||||
->maxLength(120)
|
||||
->columnSpanFull(),
|
||||
|
||||
FileUpload::make('logo')
|
||||
->label('Logo')
|
||||
->image()
|
||||
@@ -26,13 +32,14 @@ class PartnersSection
|
||||
->visibility('public')
|
||||
->required()
|
||||
->columnSpanFull(),
|
||||
|
||||
|
||||
TextInput::make('url')
|
||||
->label('Website URL')
|
||||
->url()
|
||||
->placeholder('https://example.com')
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->itemLabel(fn (array $state): ?string => $state['name'] ?? null)
|
||||
->grid(4)
|
||||
->columnSpanFull()
|
||||
->reorderableWithButtons()
|
||||
|
||||
@@ -104,10 +104,8 @@ class LandingPageSection
|
||||
Repeater::make('landing_page_data.features')
|
||||
->label(__('products.features'))
|
||||
->schema([
|
||||
Select::make('icon')
|
||||
->label(__('products.feature_icon'))
|
||||
->options($iconOptions)
|
||||
->searchable(),
|
||||
TextInput::make('icon')
|
||||
->label(__('products.feature_icon')),
|
||||
TextInput::make('title')
|
||||
->label(__('products.feature_title'))
|
||||
->columnSpanFull(),
|
||||
|
||||
@@ -25,7 +25,6 @@ class ProductForm
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label(__('products.title'))
|
||||
->required()
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(function (Get $get, Set $set, ?string $state) {
|
||||
$set('slug', Str::slug($state));
|
||||
@@ -35,15 +34,13 @@ class ProductForm
|
||||
->dehydrated(false),
|
||||
TextInput::make('slug')
|
||||
->label(__('products.slug'))
|
||||
->required()
|
||||
->unique(ignoreRecord: true),
|
||||
Select::make('type')
|
||||
->label(__('products.type'))
|
||||
->options([
|
||||
'product' => __('products.product'),
|
||||
'service' => __('products.service'),
|
||||
])
|
||||
->required(),
|
||||
]),
|
||||
Select::make('product_category_id')
|
||||
->label(__('products.category'))
|
||||
->options(function () {
|
||||
@@ -54,7 +51,6 @@ class ProductForm
|
||||
->searchable(),
|
||||
FileUpload::make('hero_image')
|
||||
->label(__('products.hero_image'))
|
||||
->required()
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('products'),
|
||||
@@ -81,7 +77,7 @@ class ProductForm
|
||||
'title' => [
|
||||
'type' => 'text',
|
||||
'label' => __('products.title'),
|
||||
'required' => true,
|
||||
'required' => false,
|
||||
],
|
||||
'content' => [
|
||||
'type' => 'richtext',
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Proposals\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Proposals\ProposalResource;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
|
||||
class CreateProposal extends CreateRecord
|
||||
{
|
||||
protected static string $resource = ProposalResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('proposal.create');
|
||||
}
|
||||
|
||||
public function getMaxContentWidth(): Width | string | null
|
||||
{
|
||||
return Width::Full;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Proposals\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Proposals\ProposalResource;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\EditRecord;
|
||||
use Filament\Support\Enums\Width;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class EditProposal extends EditRecord
|
||||
{
|
||||
protected static string $resource = ProposalResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('proposal.edit');
|
||||
}
|
||||
|
||||
public function getMaxContentWidth(): Width | string | null
|
||||
{
|
||||
return Width::Full;
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
// Teklif linkini tarayıcı panosuna kopyala
|
||||
Action::make('copy_link')
|
||||
->label(__('proposal.copy_link'))
|
||||
->icon('heroicon-o-clipboard-document-check')
|
||||
->color('info')
|
||||
->action(function () {
|
||||
$url = route('proposals.show', $this->record->slug);
|
||||
|
||||
// Livewire v3: sunucu tarafında JS enjekte et (user-gesture gerektirmez)
|
||||
$escaped = addslashes($url);
|
||||
$this->js("(function(){var t=document.createElement('textarea');t.value='{$escaped}';t.style.position='fixed';t.style.opacity='0';document.body.appendChild(t);t.focus();t.select();try{document.execCommand('copy')}catch(e){}document.body.removeChild(t);})();");
|
||||
|
||||
Notification::make()
|
||||
->title(__('proposal.link_copied'))
|
||||
->body($url)
|
||||
->success()
|
||||
->duration(6000)
|
||||
->send();
|
||||
})
|
||||
->visible(fn () => $this->record !== null),
|
||||
|
||||
// Yeni rastgele benzersiz slug üret ve kaydet
|
||||
Action::make('regenerate_slug')
|
||||
->label(__('proposal.regenerate_slug'))
|
||||
->icon('heroicon-o-arrow-path')
|
||||
->color('warning')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(__('proposal.regenerate_slug_confirm_heading'))
|
||||
->modalDescription(__('proposal.regenerate_slug_confirm_body'))
|
||||
->modalSubmitActionLabel(__('proposal.regenerate_slug_confirm_button'))
|
||||
->action(function () {
|
||||
$newSlug = 'teklif-' . strtolower(Str::random(4)) . '-' . date('ymd') . '-' . strtolower(Str::random(4));
|
||||
|
||||
// Benzersizliği garanti et
|
||||
while (\App\Models\Proposal::where('slug', $newSlug)->where('id', '!=', $this->record->id)->exists()) {
|
||||
$newSlug = 'teklif-' . strtolower(Str::random(4)) . '-' . date('ymd') . '-' . strtolower(Str::random(4));
|
||||
}
|
||||
|
||||
$this->record->update(['slug' => $newSlug]);
|
||||
|
||||
$newUrl = route('proposals.show', $newSlug);
|
||||
|
||||
Notification::make()
|
||||
->title(__('proposal.regenerate_slug_success'))
|
||||
->body($newUrl)
|
||||
->success()
|
||||
->duration(8000)
|
||||
->send();
|
||||
|
||||
// Formu yenile
|
||||
$this->fillForm();
|
||||
})
|
||||
->visible(fn () => $this->record !== null),
|
||||
|
||||
// Teklifi yeni sekmede önizle
|
||||
Action::make('preview')
|
||||
->label(__('proposal.preview'))
|
||||
->icon('heroicon-o-eye')
|
||||
->color('gray')
|
||||
->url(fn () => $this->record ? route('proposals.show', $this->record->slug) : '#')
|
||||
->openUrlInNewTab()
|
||||
->visible(fn () => $this->record !== null),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Proposals\Pages;
|
||||
|
||||
use App\Filament\Admin\Resources\Proposals\ProposalResource;
|
||||
use Filament\Actions\CreateAction;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
|
||||
class ListProposals extends ListRecords
|
||||
{
|
||||
protected static string $resource = ProposalResource::class;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('proposal.title');
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label(__('proposal.create')),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Proposals;
|
||||
|
||||
use App\Filament\Admin\Resources\Proposals\Pages\CreateProposal;
|
||||
use App\Filament\Admin\Resources\Proposals\Pages\EditProposal;
|
||||
use App\Filament\Admin\Resources\Proposals\Pages\ListProposals;
|
||||
use App\Filament\Admin\Resources\Proposals\Schemas\ProposalForm;
|
||||
use App\Filament\Admin\Resources\Proposals\Tables\ProposalsTable;
|
||||
use App\Models\Proposal;
|
||||
use BackedEnum;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
|
||||
class ProposalResource extends Resource
|
||||
{
|
||||
protected static ?string $model = Proposal::class;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-document-text';
|
||||
|
||||
public static function getNavigationLabel(): string
|
||||
{
|
||||
return __('proposal.navigation_label');
|
||||
}
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('proposal.model_label');
|
||||
}
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('proposal.plural_model_label');
|
||||
}
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return ProposalForm::configure($schema);
|
||||
}
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return ProposalsTable::configure($table);
|
||||
}
|
||||
|
||||
public static function getRelations(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPages(): array
|
||||
{
|
||||
return [
|
||||
'index' => ListProposals::route('/'),
|
||||
'create' => CreateProposal::route('/create'),
|
||||
'edit' => EditProposal::route('/{record}/edit'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRecordRouteBindingEloquentQuery(): Builder
|
||||
{
|
||||
return parent::getRecordRouteBindingEloquentQuery()
|
||||
->withoutGlobalScopes([
|
||||
SoftDeletingScope::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Proposals\Schemas;
|
||||
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Forms\Components\DatePicker;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\CodeEditor;
|
||||
use Filament\Forms\Components\CodeEditor\Enums\Language as CodeEditorLanguage;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\View;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class ProposalForm
|
||||
{
|
||||
public static function configure(Schema $schema): Schema
|
||||
{
|
||||
return $schema
|
||||
->schema([
|
||||
Tabs::make('Tabs')
|
||||
->tabs([
|
||||
Tab::make('Teklif Detayları ve İçerik')
|
||||
->icon('heroicon-m-document-text')
|
||||
->schema([
|
||||
// Section 2: side-by-side Markdown editor (CodeMirror) + live preview
|
||||
Section::make(__('proposal.content_section'))
|
||||
->description('Sol tarafta Markdown formatında içeriği düzenleyin, sağ tarafta canlı olarak nasıl görüneceğini izleyin.')
|
||||
->columns(2)
|
||||
->schema([
|
||||
CodeEditor::make('content')
|
||||
->label(__('proposal.content_field'))
|
||||
->hiddenLabel()
|
||||
->required()
|
||||
->language(CodeEditorLanguage::Markdown)
|
||||
->live(onBlur: false)
|
||||
->columnSpan(1)
|
||||
->extraAttributes([
|
||||
'style' => 'min-height: 700px; max-height: 700px; overflow-y: auto;',
|
||||
'data-field-name' => 'content',
|
||||
]),
|
||||
|
||||
View::make('filament.components.markdown-preview')
|
||||
->columnSpan(1),
|
||||
]),
|
||||
]),
|
||||
Tab::make('Durum ve Ayarlar')
|
||||
->icon('heroicon-m-cog-6-tooth')
|
||||
->schema([
|
||||
// Section 1: compact top metadata and configuration
|
||||
Section::make(__('proposal.settings_section'))
|
||||
->columns(12)
|
||||
->schema([
|
||||
TextInput::make('title')
|
||||
->label(__('proposal.title_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->live(onBlur: true)
|
||||
->afterStateUpdated(function (string $operation, $state, callable $set) {
|
||||
if ($operation !== 'create') {
|
||||
return;
|
||||
}
|
||||
$set('slug', \Str::slug($state));
|
||||
})
|
||||
->columnSpan(4),
|
||||
|
||||
TextInput::make('slug')
|
||||
->label(__('proposal.slug_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->unique(ignoreRecord: true)
|
||||
->rules(['alpha_dash'])
|
||||
->helperText(__('proposal.slug_helper'))
|
||||
->columnSpan(4),
|
||||
|
||||
Select::make('status')
|
||||
->label(__('proposal.status_field'))
|
||||
->options([
|
||||
'draft' => __('proposal.status_draft'),
|
||||
'sent' => __('proposal.status_sent'),
|
||||
'accepted' => __('proposal.status_accepted'),
|
||||
'rejected' => __('proposal.status_rejected'),
|
||||
'revised' => __('proposal.status_revised'),
|
||||
])
|
||||
->default('draft')
|
||||
->required()
|
||||
->columnSpan(4),
|
||||
|
||||
TextInput::make('client_name')
|
||||
->label(__('proposal.client_name_field'))
|
||||
->required()
|
||||
->maxLength(255)
|
||||
->columnSpan(4),
|
||||
|
||||
TextInput::make('client_email')
|
||||
->label(__('proposal.client_email_field'))
|
||||
->email()
|
||||
->maxLength(255)
|
||||
->columnSpan(4),
|
||||
|
||||
DatePicker::make('valid_until')
|
||||
->label(__('proposal.valid_until_field'))
|
||||
->native(false)
|
||||
->displayFormat('d.m.Y')
|
||||
->columnSpan(4),
|
||||
|
||||
TextInput::make('total_price')
|
||||
->label(__('proposal.total_price_field'))
|
||||
->numeric()
|
||||
->prefix(fn ($get) => match ($get('currency')) {
|
||||
'USD' => '$',
|
||||
'EUR' => '€',
|
||||
default => '₺',
|
||||
})
|
||||
->columnSpan(3),
|
||||
|
||||
Select::make('currency')
|
||||
->label(__('proposal.currency_field'))
|
||||
->options([
|
||||
'TRY' => 'TL (₺)',
|
||||
'USD' => 'USD ($)',
|
||||
'EUR' => 'EUR (€)',
|
||||
])
|
||||
->default('TRY')
|
||||
->required()
|
||||
->live()
|
||||
->columnSpan(1),
|
||||
|
||||
Select::make('meta.accent_color')
|
||||
->label(__('proposal.accent_color_field'))
|
||||
->options([
|
||||
'indigo' => 'Premium Indigo (Mor/Mavi)',
|
||||
'emerald' => 'Emerald Medical (Zümrüt Yeşil)',
|
||||
'cyberpunk' => 'Cyberpunk Neon (Pembe/Mavi)',
|
||||
'coral' => 'Coral Corporate (Mercan Kırmızı)',
|
||||
'amber' => 'Amber Executive (Kehribar/Altın)',
|
||||
])
|
||||
->default('indigo')
|
||||
->live()
|
||||
->columnSpan(4),
|
||||
|
||||
Checkbox::make('meta.show_calculator')
|
||||
->label(__('proposal.show_calculator_field'))
|
||||
->default(true)
|
||||
->columnSpan(4),
|
||||
|
||||
// Client feedback inside this section — only visible when present
|
||||
Textarea::make('client_feedback')
|
||||
->label('Müşteri Geri Bildirimi / Revizyon Notu')
|
||||
->rows(3)
|
||||
->disabled()
|
||||
->dehydrated(false)
|
||||
->placeholder('Henüz geri bildirim bırakılmadı.')
|
||||
->visible(fn ($record) => $record !== null && !empty($record->client_feedback))
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
]),
|
||||
])->columnSpanFull()
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Admin\Resources\Proposals\Tables;
|
||||
|
||||
use Filament\Actions\BulkActionGroup;
|
||||
use Filament\Actions\DeleteBulkAction;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class ProposalsTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
TextColumn::make('title')
|
||||
->label(__('proposal.table_title'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(40),
|
||||
|
||||
TextColumn::make('client_name')
|
||||
->label(__('proposal.table_client'))
|
||||
->searchable()
|
||||
->sortable()
|
||||
->limit(30),
|
||||
|
||||
TextColumn::make('total_price')
|
||||
->label(__('proposal.table_price'))
|
||||
->money(fn ($record) => $record->currency)
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('status')
|
||||
->label(__('proposal.table_status'))
|
||||
->badge()
|
||||
->color(fn (string $state): string => match ($state) {
|
||||
'draft' => 'gray',
|
||||
'sent' => 'info',
|
||||
'accepted' => 'success',
|
||||
'rejected' => 'danger',
|
||||
'revised' => 'warning',
|
||||
default => 'gray',
|
||||
})
|
||||
->formatStateUsing(fn (string $state): string => match ($state) {
|
||||
'draft' => __('proposal.status_draft'),
|
||||
'sent' => __('proposal.status_sent'),
|
||||
'accepted' => __('proposal.status_accepted'),
|
||||
'rejected' => __('proposal.status_rejected'),
|
||||
'revised' => __('proposal.status_revised'),
|
||||
default => $state,
|
||||
}),
|
||||
|
||||
TextColumn::make('slug')
|
||||
->label(__('proposal.table_slug'))
|
||||
->searchable()
|
||||
->copyable()
|
||||
->copyMessage(__('proposal.link_copied'))
|
||||
->icon('heroicon-o-document-duplicate')
|
||||
->limit(25),
|
||||
|
||||
TextColumn::make('valid_until')
|
||||
->label(__('proposal.table_valid_until'))
|
||||
->date('d.m.Y')
|
||||
->sortable(),
|
||||
|
||||
TextColumn::make('views_count')
|
||||
->label(__('proposal.table_views'))
|
||||
->sortable()
|
||||
->alignCenter(),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->label(__('proposal.table_created_at'))
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable()
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
])
|
||||
->filters([
|
||||
SelectFilter::make('status')
|
||||
->label(__('proposal.status_field'))
|
||||
->options([
|
||||
'draft' => __('proposal.status_draft'),
|
||||
'sent' => __('proposal.status_sent'),
|
||||
'accepted' => __('proposal.status_accepted'),
|
||||
'rejected' => __('proposal.status_rejected'),
|
||||
'revised' => __('proposal.status_revised'),
|
||||
]),
|
||||
])
|
||||
->recordActions([
|
||||
EditAction::make()
|
||||
->label(__('proposal.edit')),
|
||||
])
|
||||
->actions([
|
||||
Action::make('view_proposal')
|
||||
->label(__('proposal.preview'))
|
||||
->icon('heroicon-o-eye')
|
||||
->url(fn ($record) => route('proposals.show', $record->slug))
|
||||
->openUrlInNewTab(),
|
||||
])
|
||||
->bulkActions([
|
||||
BulkActionGroup::make([
|
||||
DeleteBulkAction::make()
|
||||
->label(__('proposal.delete')),
|
||||
]),
|
||||
])
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,12 @@ class EditSetting extends EditRecord
|
||||
|
||||
if (isset($data['type'])) {
|
||||
$type = $data['type'];
|
||||
$virtualField = 'value_' . $type;
|
||||
|
||||
// Map the setting type to the form field name
|
||||
$virtualField = match ($type) {
|
||||
'string', 'text', 'json', 'integer', 'float' => 'value_text',
|
||||
default => 'value_' . $type,
|
||||
};
|
||||
|
||||
try {
|
||||
// Model accessor'ını kullanarak değeri al
|
||||
|
||||
@@ -115,7 +115,7 @@ class SettingForm
|
||||
->label(__('settings.value'))
|
||||
->helperText(__('settings.value_helper'))
|
||||
->required()
|
||||
->visible(fn (Get $get) => in_array($get('type'), ['string', 'text', 'json']) && !in_array($get('key'), ['default_header', 'default_footer']))
|
||||
->visible(fn (Get $get) => in_array($get('type'), ['string', 'text', 'json', 'integer', 'float']) && !in_array($get('key'), ['default_header', 'default_footer']))
|
||||
->rows(fn (Get $get) => $get('type') === 'text' ? 5 : 3)
|
||||
->columnSpanFull(),
|
||||
|
||||
|
||||
@@ -222,6 +222,93 @@ if (!function_exists('get_translation_status_color')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('flag_emoji_from_country_code')) {
|
||||
/**
|
||||
* Build a flag emoji from a two-letter country/region code (e.g. TR → 🇹🇷).
|
||||
*/
|
||||
function flag_emoji_from_country_code(string $countryCode): string
|
||||
{
|
||||
$countryCode = strtoupper(substr($countryCode, 0, 2));
|
||||
$flag = '';
|
||||
|
||||
for ($i = 0; $i < strlen($countryCode); $i++) {
|
||||
$flag .= mb_chr(127397 + ord($countryCode[$i]));
|
||||
}
|
||||
|
||||
return $flag;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('country_code_from_flag_emoji')) {
|
||||
/**
|
||||
* Extract a two-letter code from a flag emoji (e.g. 🇬🇧 → GB).
|
||||
*/
|
||||
function country_code_from_flag_emoji(?string $flag): ?string
|
||||
{
|
||||
if (empty($flag)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$code = '';
|
||||
|
||||
foreach (mb_str_split($flag) as $char) {
|
||||
$ord = mb_ord($char);
|
||||
|
||||
if ($ord >= 0x1F1E6 && $ord <= 0x1F1FF) {
|
||||
$code .= chr(ord('A') + ($ord - 0x1F1E6));
|
||||
}
|
||||
}
|
||||
|
||||
return strlen($code) === 2 ? $code : null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('resolve_language_flag')) {
|
||||
/**
|
||||
* Resolve flag emoji for a language (stored flag or generated from code).
|
||||
*/
|
||||
function resolve_language_flag(Language|string $language): string
|
||||
{
|
||||
if (is_string($language)) {
|
||||
$language = Language::findByCode($language);
|
||||
}
|
||||
|
||||
if (! $language) {
|
||||
return '🌐';
|
||||
}
|
||||
|
||||
if (! empty($language->flag) && $language->flag !== '🌐') {
|
||||
return $language->flag;
|
||||
}
|
||||
|
||||
return flag_emoji_from_country_code($language->code);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_language_short_code')) {
|
||||
/**
|
||||
* Short display code for language (from flag region, e.g. GB, TR, SE).
|
||||
*/
|
||||
function get_language_short_code(Language|string $language): string
|
||||
{
|
||||
if (is_string($language)) {
|
||||
$model = Language::findByCode($language);
|
||||
|
||||
return $model ? get_language_short_code($model) : strtoupper($language);
|
||||
}
|
||||
|
||||
$fromFlag = country_code_from_flag_emoji($language->flag);
|
||||
|
||||
if ($fromFlag) {
|
||||
return $fromFlag;
|
||||
}
|
||||
|
||||
$fromResolved = country_code_from_flag_emoji(resolve_language_flag($language));
|
||||
|
||||
return $fromResolved ?: strtoupper($language->code);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_language_flag')) {
|
||||
/**
|
||||
* Get language flag emoji or icon
|
||||
@@ -229,7 +316,8 @@ if (!function_exists('get_language_flag')) {
|
||||
function get_language_flag(string $languageCode): string
|
||||
{
|
||||
$language = Language::findByCode($languageCode);
|
||||
return $language ? ($language->flag ?? '🌐') : '🌐';
|
||||
|
||||
return $language ? resolve_language_flag($language) : '🌐';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,3 +384,26 @@ if (!function_exists('t')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('locale_upper')) {
|
||||
/**
|
||||
* Locale-aware uppercase (Turkish: i→İ, ı→I, etc.)
|
||||
*/
|
||||
function locale_upper(string $text, ?string $locale = null): string
|
||||
{
|
||||
$locale = $locale ?? app()->getLocale();
|
||||
|
||||
if (str_starts_with($locale, 'tr')) {
|
||||
return mb_strtoupper(
|
||||
str_replace(
|
||||
['i', 'ı', 'ğ', 'ü', 'ş', 'ö', 'ç'],
|
||||
['İ', 'I', 'Ğ', 'Ü', 'Ş', 'Ö', 'Ç'],
|
||||
$text
|
||||
),
|
||||
'UTF-8'
|
||||
);
|
||||
}
|
||||
|
||||
return mb_strtoupper($text, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\HeaderTemplate;
|
||||
use App\Models\FooterTemplate;
|
||||
use App\Services\TemplateService;
|
||||
use App\Models\Page;
|
||||
use App\Support\BlogStructuredData;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
@@ -41,6 +42,16 @@ class BlogController extends Controller
|
||||
$query = $query->whereJsonContains('tags', $request->tag);
|
||||
}
|
||||
|
||||
// Search filtresi
|
||||
if ($query && $request->has('search') && $request->search) {
|
||||
$search = $request->search;
|
||||
$query = $query->where(function($q) use ($search) {
|
||||
$q->where('title', 'like', '%' . $search . '%')
|
||||
->orWhere('content', 'like', '%' . $search . '%')
|
||||
->orWhere('excerpt', 'like', '%' . $search . '%');
|
||||
});
|
||||
}
|
||||
|
||||
$posts = $query
|
||||
? $query->latest('published_at')->paginate(12)
|
||||
: collect();
|
||||
@@ -109,22 +120,101 @@ class BlogController extends Controller
|
||||
// Kategorileri al (filtreleme için)
|
||||
$categories = class_exists(\App\Models\BlogCategory::class) ? \App\Models\BlogCategory::where('is_active', true)->orderBy('sort_order')->get() : collect();
|
||||
|
||||
// Elemis Widget'ları için veri hazırlığı
|
||||
$carouselPosts = collect();
|
||||
$highlightCategories = collect();
|
||||
$popularPosts = collect();
|
||||
$sidebarCategories = collect();
|
||||
$tags = collect();
|
||||
|
||||
if (!$request->ajax()) {
|
||||
if (class_exists(Blog::class)) {
|
||||
// Karusel: Öne çıkarılan ya da en güncel 5 yazı
|
||||
$carouselPosts = Blog::with(['category', 'author'])
|
||||
->published()
|
||||
->featured()
|
||||
->latest('published_at')
|
||||
->take(5)
|
||||
->get();
|
||||
if ($carouselPosts->isEmpty()) {
|
||||
$carouselPosts = Blog::with(['category', 'author'])
|
||||
->published()
|
||||
->latest('published_at')
|
||||
->take(5)
|
||||
->get();
|
||||
}
|
||||
|
||||
// Popüler Yazılar: En çok okunan 3 yazı
|
||||
$popularPosts = Blog::with(['category', 'author'])
|
||||
->published()
|
||||
->orderBy('view_count', 'desc')
|
||||
->take(3)
|
||||
->get();
|
||||
|
||||
// Etiket Bulutu: Tüm yayınlanan yazılardaki benzersiz etiketler
|
||||
$allTags = Blog::published()
|
||||
->whereNotNull('tags')
|
||||
->pluck('tags');
|
||||
$tagsCollected = collect();
|
||||
foreach ($allTags as $postTags) {
|
||||
if (is_array($postTags)) {
|
||||
foreach ($postTags as $tag) {
|
||||
$tagsCollected->push($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
$tags = $tagsCollected->unique()->take(15);
|
||||
}
|
||||
|
||||
if (class_exists(\App\Models\BlogCategory::class)) {
|
||||
// Hoş Geldiniz bölümü altındaki 4'lü kategori vurgusu
|
||||
$highlightCategories = \App\Models\BlogCategory::where('is_active', true)
|
||||
->withCount('blogs')
|
||||
->has('blogs')
|
||||
->orderBy('sort_order')
|
||||
->take(4)
|
||||
->get();
|
||||
if ($highlightCategories->count() < 4) {
|
||||
$highlightCategories = \App\Models\BlogCategory::where('is_active', true)
|
||||
->orderBy('sort_order')
|
||||
->take(4)
|
||||
->get();
|
||||
}
|
||||
|
||||
// Sidebar Kategorileri: Yazı sayısıyla birlikte
|
||||
$sidebarCategories = \App\Models\BlogCategory::where('is_active', true)
|
||||
->withCount('blogs')
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->ajax()) {
|
||||
return view('blog.partials.posts', [
|
||||
'posts' => $posts
|
||||
]);
|
||||
}
|
||||
|
||||
$pageUrl = url()->current();
|
||||
|
||||
return view('blog.index', [
|
||||
'settings' => $settings,
|
||||
'posts' => $posts,
|
||||
'categories' => $categories,
|
||||
'carouselPosts' => $carouselPosts,
|
||||
'highlightCategories' => $highlightCategories,
|
||||
'popularPosts' => $popularPosts,
|
||||
'sidebarCategories' => $sidebarCategories,
|
||||
'tags' => $tags,
|
||||
'renderedHeader' => $renderedHeader,
|
||||
'renderedFooter' => $renderedFooter,
|
||||
'meta' => [
|
||||
'title' => __('blog.meta-index-title'),
|
||||
'description' => __('blog.meta-index-description'),
|
||||
],
|
||||
'structuredData' => $posts instanceof \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
? BlogStructuredData::forIndex($posts, $pageUrl)
|
||||
: null,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -215,6 +305,8 @@ class BlogController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
$pageUrl = route('blog.show', $post->slug);
|
||||
|
||||
return view('blog.show', [
|
||||
'settings' => $settings,
|
||||
'post' => $post,
|
||||
@@ -226,7 +318,9 @@ class BlogController extends Controller
|
||||
'title' => method_exists($post, 'translate') ? ($post->translate('meta_title') ?: $post->translate('title')) : ($post->meta_title ?? $post->title),
|
||||
'description' => method_exists($post, 'translate') ? ($post->translate('meta_description') ?: $post->translate('excerpt')) : ($post->meta_description ?? $post->excerpt),
|
||||
'image' => $post->featured_image ? asset('storage/' . $post->featured_image) : null,
|
||||
'og_type' => 'article',
|
||||
],
|
||||
'structuredData' => BlogStructuredData::forShow($post, $pageUrl),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ class CareerController extends Controller
|
||||
if ($request->type === 'job') {
|
||||
$rules['nda'] = 'required|file|mimes:pdf|max:5120';
|
||||
$rules['contract'] = 'required|file|mimes:pdf|max:5120';
|
||||
$rules['id_photocopy'] = 'required|file|mimes:pdf,jpg,jpeg,png|max:5120'; // Max 5MB
|
||||
$rules['git_knowledge'] = 'accepted';
|
||||
$rules['ai_usage'] = 'accepted';
|
||||
}
|
||||
@@ -50,6 +51,7 @@ class CareerController extends Controller
|
||||
$cvPath = $request->file('cv') ? $request->file('cv')->store('cvs', 'public') : null;
|
||||
$ndaPath = $request->file('nda') ? $request->file('nda')->store('ndas', 'public') : null;
|
||||
$contractPath = $request->file('contract') ? $request->file('contract')->store('contracts', 'public') : null;
|
||||
$idPhotocopyPath = $request->file('id_photocopy') ? $request->file('id_photocopy')->store('id_photocopies', 'public') : null;
|
||||
|
||||
CareerApplication::create([
|
||||
'name' => $request->name,
|
||||
@@ -59,6 +61,7 @@ class CareerController extends Controller
|
||||
'cv_path' => $cvPath,
|
||||
'nda_path' => $ndaPath,
|
||||
'contract_path' => $contractPath,
|
||||
'id_photocopy_path' => $idPhotocopyPath,
|
||||
'git_knowledge' => $request->has('git_knowledge'),
|
||||
'ai_usage' => $request->has('ai_usage'),
|
||||
'message' => $request->message,
|
||||
@@ -74,4 +77,239 @@ class CareerController extends Controller
|
||||
|
||||
return redirect()->back()->with('success', __('career.success_message'));
|
||||
}
|
||||
|
||||
public function internLoginForm()
|
||||
{
|
||||
if (session()->has('intern_id')) {
|
||||
return redirect()->route('intern.dashboard');
|
||||
}
|
||||
return view('front.career.intern_login', [
|
||||
'meta' => [
|
||||
'title' => 'Stajyer Girişi',
|
||||
'description' => 'İmzalı staj belgelerinize erişmek için lütfen giriş yapın.',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function internLogin(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
$intern = CareerApplication::where('username', $request->username)
|
||||
->where('type', 'internship')
|
||||
->first();
|
||||
|
||||
if (!$intern || !\Illuminate\Support\Facades\Hash::check($request->password, $intern->password)) {
|
||||
return redirect()->back()
|
||||
->withInput($request->only('username'))
|
||||
->withErrors([
|
||||
'username' => 'Girdiğiniz kullanıcı adı veya şifre hatalı.',
|
||||
]);
|
||||
}
|
||||
|
||||
session(['intern_id' => $intern->id]);
|
||||
|
||||
return redirect()->route('intern.dashboard')->with('success', 'Başarıyla giriş yapıldı.');
|
||||
}
|
||||
|
||||
public function internDashboard()
|
||||
{
|
||||
if (!session()->has('intern_id')) {
|
||||
return redirect()->route('intern.login')->with('error', 'Lütfen giriş yapın.');
|
||||
}
|
||||
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
return view('front.career.intern_dashboard', [
|
||||
'intern' => $intern,
|
||||
'meta' => [
|
||||
'title' => 'Stajyer Paneli',
|
||||
'description' => 'Belgelerinizi buradan indirebilirsiniz.',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function uploadInternshipForm(Request $request)
|
||||
{
|
||||
if (!session()->has('intern_id')) {
|
||||
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'internship_form' => 'required|file|mimes:pdf,docx,jpg,png,jpeg|max:51200',
|
||||
'internship_start_date' => 'required|date',
|
||||
'internship_total_days' => 'required|integer|min:1',
|
||||
], [
|
||||
'internship_form.required' => 'Lütfen bir dosya seçin.',
|
||||
'internship_form.file' => 'Yüklenen öğe geçerli bir dosya olmalıdır.',
|
||||
'internship_form.mimes' => 'Yalnızca PDF, DOCX, JPG ve PNG formatındaki dosyalar kabul edilir.',
|
||||
'internship_form.max' => 'Dosya boyutu en fazla 50MB olabilir.',
|
||||
'internship_start_date.required' => 'Lütfen staj başlangıç tarihini girin.',
|
||||
'internship_start_date.date' => 'Geçerli bir başlangıç tarihi girin.',
|
||||
'internship_total_days.required' => 'Lütfen staj süresini girin.',
|
||||
'internship_total_days.integer' => 'Staj süresi geçerli bir tam sayı olmalıdır.',
|
||||
'internship_total_days.min' => 'Staj süresi en az 1 gün olmalıdır.',
|
||||
]);
|
||||
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
|
||||
if ($request->hasFile('internship_form')) {
|
||||
// Delete old file if exists
|
||||
if ($intern->to_be_signed_internship_form_path) {
|
||||
Storage::disk('public')->delete($intern->to_be_signed_internship_form_path);
|
||||
}
|
||||
|
||||
$path = $request->file('internship_form')->store('to_be_signed_interns', 'public');
|
||||
|
||||
// Calculate end date based on weekdays and duration
|
||||
$startDate = \Carbon\Carbon::parse($request->internship_start_date);
|
||||
$daysToAdd = intval($request->internship_total_days);
|
||||
|
||||
$endDate = $startDate->copy();
|
||||
$count = 0;
|
||||
$temp = $startDate->copy();
|
||||
|
||||
while ($count < $daysToAdd) {
|
||||
if ($temp->isWeekend()) {
|
||||
$temp->addDay();
|
||||
continue;
|
||||
}
|
||||
$endDate = $temp->copy();
|
||||
$temp->addDay();
|
||||
$count++;
|
||||
}
|
||||
|
||||
$intern->update([
|
||||
'to_be_signed_internship_form_path' => $path,
|
||||
'internship_start_date' => $request->internship_start_date,
|
||||
'internship_end_date' => $endDate->format('Y-m-d'),
|
||||
'internship_total_days' => $daysToAdd,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'İmzalanacak staj formunuz ve staj tarihleri başarıyla kaydedildi.');
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', 'Dosya yüklenirken bir hata oluştu.');
|
||||
}
|
||||
|
||||
public function saveGithubRepo(Request $request)
|
||||
{
|
||||
if (!session()->has('intern_id')) {
|
||||
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'github_repo' => 'required|string|url|max:255',
|
||||
], [
|
||||
'github_repo.required' => 'Lütfen Github depo URL\'sini girin.',
|
||||
'github_repo.url' => 'Geçerli bir URL girilmelidir.',
|
||||
'github_repo.max' => 'Github depo URL\'si en fazla 255 karakter olabilir.',
|
||||
]);
|
||||
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
|
||||
// Sanitize & format github URL
|
||||
$repoUrl = trim($request->github_repo);
|
||||
|
||||
$intern->update([
|
||||
'github_repo' => $repoUrl
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('success', 'Github deposu başarıyla güncellendi.');
|
||||
}
|
||||
|
||||
public function downloadMarkdown()
|
||||
{
|
||||
if (!session()->has('intern_id')) {
|
||||
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
|
||||
}
|
||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||
$repo = $intern->github_repo;
|
||||
if (!$repo) {
|
||||
return redirect()->back()->with('error', 'Lütfen önce Github deposunu tanımlayın.');
|
||||
}
|
||||
|
||||
// Parse owner and repo
|
||||
// E.g., https://github.com/owner/repo or owner/repo
|
||||
$repoClean = str_replace(['https://github.com/', 'http://github.com/', 'https://www.github.com/', 'http://www.github.com/'], '', $repo);
|
||||
$repoClean = trim($repoClean, '/');
|
||||
$parts = explode('/', $repoClean);
|
||||
if (count($parts) < 2) {
|
||||
return redirect()->back()->with('error', 'Geçersiz Github depo formatı.');
|
||||
}
|
||||
$owner = $parts[0];
|
||||
$repoName = explode('.', $parts[1])[0]; // Remove .git if exists
|
||||
|
||||
// Fetch commits
|
||||
$url = "https://api.github.com/repos/{$owner}/{$repoName}/commits?per_page=100";
|
||||
|
||||
$opts = [
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => [
|
||||
'User-Agent: PHP-Github-Client',
|
||||
]
|
||||
]
|
||||
];
|
||||
$context = stream_context_create($opts);
|
||||
$response = @file_get_contents($url, false, $context);
|
||||
|
||||
if ($response === false) {
|
||||
return redirect()->back()->with('error', 'Github API\'den commit bilgileri alınamadı. Deponuzun public olduğundan emin olun.');
|
||||
}
|
||||
|
||||
$commits = json_decode($response, true);
|
||||
if (!is_array($commits)) {
|
||||
return redirect()->back()->with('error', 'Github API yanıtı çözümlenemedi.');
|
||||
}
|
||||
|
||||
// Sort commits chronological (ascending)
|
||||
$commits = array_reverse($commits);
|
||||
|
||||
// Group commits by date (YYYY-MM-DD)
|
||||
$grouped = [];
|
||||
foreach ($commits as $c) {
|
||||
$dateStr = $c['commit']['author']['date'] ?? '';
|
||||
if ($dateStr) {
|
||||
$dateOnly = \Carbon\Carbon::parse($dateStr)->format('Y-m-d');
|
||||
$grouped[$dateOnly][] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort dates chronological (ascending)
|
||||
ksort($grouped);
|
||||
|
||||
// Generate Markdown content
|
||||
$md = "# Staj Günlüğü - " . $intern->name . "\n";
|
||||
$md .= "Depo: https://github.com/{$owner}/{$repoName}\n\n";
|
||||
|
||||
$day = 1;
|
||||
foreach ($grouped as $date => $dayCommits) {
|
||||
$formattedDate = \Carbon\Carbon::parse($date)->format('d.m.Y');
|
||||
$md .= "## {$day}. Gün ({$formattedDate})\n";
|
||||
foreach ($dayCommits as $c) {
|
||||
$message = trim($c['commit']['message'] ?? '');
|
||||
$time = \Carbon\Carbon::parse($c['commit']['author']['date'] ?? '')->format('H:i');
|
||||
$hash = substr($c['sha'] ?? '', 0, 7);
|
||||
$md .= "- [{$time}] `{$hash}`: {$message}\n";
|
||||
}
|
||||
$md .= "\n";
|
||||
$day++;
|
||||
}
|
||||
|
||||
$fileName = str()->slug($intern->name) . "-staj-gunlugu.md";
|
||||
|
||||
return response($md, 200, [
|
||||
'Content-Type' => 'text/markdown; charset=UTF-8',
|
||||
'Content-Disposition' => 'attachment; filename="' . $fileName . '"',
|
||||
]);
|
||||
}
|
||||
|
||||
public function internLogout()
|
||||
{
|
||||
session()->forget('intern_id');
|
||||
return redirect()->route('intern.login')->with('success', 'Başarıyla çıkış yapıldı.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\MusicProduction;
|
||||
use App\Models\Setting;
|
||||
use App\Models\FooterTemplate;
|
||||
use App\Services\TemplateService;
|
||||
use App\Support\MusicProductionStructuredData;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MusicProductionController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
// Get Settings
|
||||
$settings = new \stdClass();
|
||||
$allSettings = Setting::query()->where('is_active', true)->get();
|
||||
foreach ($allSettings as $setting) {
|
||||
$settings->{$setting->key} = $setting->value;
|
||||
}
|
||||
|
||||
$search = trim((string) request('q', ''));
|
||||
|
||||
$productionsQuery = MusicProduction::active()->ordered();
|
||||
|
||||
if ($search !== '') {
|
||||
$locale = app()->getLocale();
|
||||
|
||||
$productionsQuery->where(function ($query) use ($search, $locale) {
|
||||
$query->where('title', 'like', "%{$search}%")
|
||||
->orWhere('client_name', 'like', "%{$search}%")
|
||||
->orWhere('slug', 'like', "%{$search}%")
|
||||
->orWhereHas('translations', function ($translationQuery) use ($search, $locale) {
|
||||
$translationQuery
|
||||
->where('status', 'published')
|
||||
->whereIn('field_name', ['title', 'client_name', 'content'])
|
||||
->where('field_value', 'like', "%{$search}%")
|
||||
->where('language_code', $locale);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$productions = $productionsQuery->paginate(9)->withQueryString();
|
||||
|
||||
// --- Footer Logic ---
|
||||
$renderedFooter = null;
|
||||
$defaultFooterId = $settings->default_footer ?? null;
|
||||
|
||||
if ($defaultFooterId) {
|
||||
$footerTemplate = FooterTemplate::query()->find($defaultFooterId);
|
||||
if ($footerTemplate) {
|
||||
$templateDefaults = $footerTemplate->default_data ?? [];
|
||||
$mergedFooterData = array_merge($templateDefaults, []);
|
||||
|
||||
$renderedFooter = TemplateService::replacePlaceholders(
|
||||
$footerTemplate->html_content,
|
||||
$mergedFooterData,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$canonicalUrl = route('music-productions.index');
|
||||
$pageUrl = $search !== '' ? $canonicalUrl : url()->current();
|
||||
|
||||
$meta = [
|
||||
'title' => __('music_productions.meta-index-title'),
|
||||
'description' => __('music_productions.meta-index-description'),
|
||||
'image' => asset('assets/music-production.png'),
|
||||
'image_alt' => __('music_productions.meta-index-title'),
|
||||
'canonical' => $canonicalUrl,
|
||||
'robots' => $search !== '' ? 'noindex, follow' : 'index, follow',
|
||||
'og_type' => 'website',
|
||||
'locale' => app()->getLocale(),
|
||||
];
|
||||
|
||||
$structuredData = MusicProductionStructuredData::forIndex($productions, $pageUrl);
|
||||
|
||||
return view('front.music-productions.index', [
|
||||
'productions' => $productions,
|
||||
'search' => $search,
|
||||
'settings' => $settings,
|
||||
'header' => 'partials.header-center-nav',
|
||||
'renderedFooter' => $renderedFooter,
|
||||
'meta' => $meta,
|
||||
'structuredData' => $structuredData,
|
||||
]);
|
||||
}
|
||||
|
||||
public function show($slug)
|
||||
{
|
||||
$production = MusicProduction::active()
|
||||
->where('slug', $slug)
|
||||
->firstOrFail();
|
||||
|
||||
// Get Settings
|
||||
$settings = new \stdClass();
|
||||
$allSettings = Setting::query()->where('is_active', true)->get();
|
||||
foreach ($allSettings as $setting) {
|
||||
$settings->{$setting->key} = $setting->value;
|
||||
}
|
||||
|
||||
// Fetch adjacent posts (Prev and Next)
|
||||
$prev = MusicProduction::active()
|
||||
->where(function($q) use ($production) {
|
||||
$q->where('sort_order', '<', $production->sort_order)
|
||||
->orWhere(function($sub) use ($production) {
|
||||
$sub->where('sort_order', $production->sort_order)
|
||||
->where('id', '<', $production->id);
|
||||
});
|
||||
})
|
||||
->orderBy('sort_order', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
$next = MusicProduction::active()
|
||||
->where(function($q) use ($production) {
|
||||
$q->where('sort_order', '>', $production->sort_order)
|
||||
->orWhere(function($sub) use ($production) {
|
||||
$sub->where('sort_order', $production->sort_order)
|
||||
->where('id', '>', $production->id);
|
||||
});
|
||||
})
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('id', 'asc')
|
||||
->first();
|
||||
|
||||
// --- Footer Logic ---
|
||||
$renderedFooter = null;
|
||||
$defaultFooterId = $settings->default_footer ?? null;
|
||||
|
||||
if ($defaultFooterId) {
|
||||
$footerTemplate = FooterTemplate::query()->find($defaultFooterId);
|
||||
if ($footerTemplate) {
|
||||
$templateDefaults = $footerTemplate->default_data ?? [];
|
||||
$mergedFooterData = array_merge($templateDefaults, []);
|
||||
|
||||
$renderedFooter = TemplateService::replacePlaceholders(
|
||||
$footerTemplate->html_content,
|
||||
$mergedFooterData,
|
||||
$production
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$pageUrl = route('music-productions.show', $production->slug);
|
||||
$title = $production->translate('title');
|
||||
$description = Str::limit(strip_tags((string) $production->translate('content')), 160)
|
||||
?: __('music_productions.meta-index-description');
|
||||
|
||||
$meta = [
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'image' => $production->cover_image_url ?: asset('assets/music-production.png'),
|
||||
'image_alt' => $title,
|
||||
'canonical' => $pageUrl,
|
||||
'robots' => 'index, follow',
|
||||
'og_type' => 'article',
|
||||
'locale' => app()->getLocale(),
|
||||
];
|
||||
|
||||
$structuredData = MusicProductionStructuredData::forShow($production, $pageUrl);
|
||||
|
||||
return view('front.music-productions.show', [
|
||||
'production' => $production,
|
||||
'prev' => $prev,
|
||||
'next' => $next,
|
||||
'settings' => $settings,
|
||||
'header' => 'partials.header-center-nav',
|
||||
'renderedFooter' => $renderedFooter,
|
||||
'meta' => $meta,
|
||||
'structuredData' => $structuredData,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ use App\Models\Setting;
|
||||
use App\Models\HeaderTemplate;
|
||||
use App\Models\FooterTemplate;
|
||||
use App\Services\TemplateService;
|
||||
use App\Support\PageStructuredData;
|
||||
use App\Support\PageTemplateHero;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -32,10 +34,13 @@ class PageController extends Controller
|
||||
|
||||
// Hiç sayfa yoksa fallback
|
||||
if (!$page) {
|
||||
$meta = $this->getMeta(null, $settings);
|
||||
|
||||
return view('templates.home', [
|
||||
'page' => null,
|
||||
'settings' => $settings,
|
||||
'meta' => $this->getMeta(null, $settings),
|
||||
'meta' => $meta,
|
||||
'structuredData' => PageStructuredData::forPage(null, url('/'), $meta['title'] ?? null, $meta['description'] ?? null),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -66,28 +71,15 @@ class PageController extends Controller
|
||||
return $this->handleShow('truncgil-akademi');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified show logic
|
||||
*/
|
||||
protected function handleShow($slug)
|
||||
{
|
||||
$settings = $this->getSettings();
|
||||
|
||||
// 3a. Akademi Sayfası Yönlendirmeleri
|
||||
if ($slug === 'truncgil-akademi') {
|
||||
$host = request()->getHost();
|
||||
$path = request()->path();
|
||||
|
||||
// Eğer ana domainden geliyorsa -> Subdomain'e yönlendir
|
||||
if ($host !== 'akademi.truncgil.com.tr') {
|
||||
return redirect()->to('https://akademi.truncgil.com.tr/', 301);
|
||||
}
|
||||
|
||||
// Eğer subdoman'de ama slug ile geliyorsa (/truncgil-akademi) -> Ana dizine yönlendir (/)
|
||||
if ($host === 'akademi.truncgil.com.tr' && $path !== '/') {
|
||||
return redirect()->to('https://akademi.truncgil.com.tr/', 301);
|
||||
}
|
||||
if ($slug === 'akademi') {
|
||||
$slug = 'truncgil-akademi';
|
||||
}
|
||||
|
||||
// Redirection logic disabled for rendering the page locally on main domain.
|
||||
|
||||
// 1. Statik View Kontrolü
|
||||
if (view()->exists("templates.$slug")) {
|
||||
@@ -136,14 +128,24 @@ class PageController extends Controller
|
||||
$renderedFooter = $this->renderFooter($page, $settings);
|
||||
}
|
||||
|
||||
$meta = $this->getMeta($page, $settings);
|
||||
$pageUrl = url()->current();
|
||||
|
||||
return view($view, [
|
||||
'page' => $page,
|
||||
'pageHero' => $page ? PageTemplateHero::resolveForPage($page) : null,
|
||||
'settings' => $settings,
|
||||
'sections' => $page ? ($page->parsed_sections ?? $page->sections ?? $page->data ?? []) : [],
|
||||
'templatedSections' => $page ? ($page->templated_sections ?? collect([])) : collect([]),
|
||||
'renderedHeader' => $renderedHeader,
|
||||
'renderedFooter' => $renderedFooter,
|
||||
'meta' => $this->getMeta($page, $settings),
|
||||
'meta' => $meta,
|
||||
'structuredData' => PageStructuredData::forPage(
|
||||
$page,
|
||||
$pageUrl,
|
||||
$meta['title'] ?? null,
|
||||
$meta['description'] ?? null,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -179,8 +181,8 @@ class PageController extends Controller
|
||||
? ($page->translate('meta_description') ?: ($page->excerpt ?? null))
|
||||
: ($page->meta_description ?? $page->excerpt ?? null);
|
||||
|
||||
if ($page->featured_image) {
|
||||
$metaImage = asset('storage/' . $page->featured_image);
|
||||
if ($page->featured_image_url) {
|
||||
$metaImage = $page->featured_image_url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Proposal;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProposalController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the public proposal presentation page.
|
||||
*/
|
||||
public function show(string $slug)
|
||||
{
|
||||
$proposal = Proposal::where('slug', $slug)->firstOrFail();
|
||||
|
||||
// Increment views count cleanly
|
||||
$proposal->increment('views_count');
|
||||
|
||||
return view('proposals.show', compact('proposal'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client interaction actions (approve or revision/feedback).
|
||||
*/
|
||||
public function action(Request $request, string $slug)
|
||||
{
|
||||
$proposal = Proposal::where('slug', $slug)->firstOrFail();
|
||||
|
||||
$actionType = $request->input('action_type');
|
||||
|
||||
if ($actionType === 'approve') {
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
$proposal->update([
|
||||
'status' => 'accepted',
|
||||
'accepted_name' => $request->input('name'),
|
||||
'accepted_at' => now(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Teklif başarıyla onaylandı! Katılımınız ve iş birliğiniz için teşekkür ederiz.',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($actionType === 'feedback') {
|
||||
$request->validate([
|
||||
'feedback' => 'required|string|max:5000',
|
||||
]);
|
||||
|
||||
$proposal->update([
|
||||
'status' => 'revised',
|
||||
'client_feedback' => $request->input('feedback'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Revizyon ve görüşleriniz başarıyla iletildi. En kısa sürede güncellenerek sizinle paylaşılacaktır.',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Geçersiz işlem tipi.',
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
@@ -41,11 +41,14 @@ class EnsureSecurityHeaders
|
||||
// For general sites: geolocation=(), microphone=(), camera=() is often safe.
|
||||
$response->headers->set('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
||||
|
||||
// Cross-Origin-Opener-Policy: isolate browsing context from cross-origin documents
|
||||
$response->headers->set('Cross-Origin-Opener-Policy', 'same-origin');
|
||||
|
||||
// Content-Security-Policy (CSP) - Start with upgrade-insecure-requests to force HTTPS assets
|
||||
// A full CSP can be tricky and break scripts. 'upgrade-insecure-requests' is safe and good for mixed content.
|
||||
// We can add 'frame-ancestors' here too if X-Frame-Options is ignored by some.
|
||||
// If the user wants A+, a basic CSP is often enough.
|
||||
$csp = "upgrade-insecure-requests; block-all-mixed-content;";
|
||||
$csp = "upgrade-insecure-requests; block-all-mixed-content; frame-ancestors 'self';";
|
||||
$response->headers->set('Content-Security-Policy', $csp);
|
||||
|
||||
return $response;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class InternJournalTimeline extends Component
|
||||
{
|
||||
public ?Model $record = null;
|
||||
|
||||
public function getGithubRepoProperty(): ?string
|
||||
{
|
||||
return $this->record?->github_repo;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.intern-journal-timeline');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Award extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasTranslations;
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'description',
|
||||
'issuer',
|
||||
'award_date',
|
||||
'image',
|
||||
'external_link',
|
||||
'is_featured',
|
||||
'is_active',
|
||||
'sort_order',
|
||||
'category',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $translatable = [
|
||||
'title',
|
||||
'description',
|
||||
'issuer',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'award_date' => 'date',
|
||||
'is_featured' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeFeatured($query)
|
||||
{
|
||||
return $query->where('is_featured', true);
|
||||
}
|
||||
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('award_date', 'desc');
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,30 @@ class CareerApplication extends Model
|
||||
'cv_path',
|
||||
'nda_path',
|
||||
'contract_path',
|
||||
'id_photocopy_path',
|
||||
'git_knowledge',
|
||||
'ai_usage',
|
||||
'message',
|
||||
'status',
|
||||
'username',
|
||||
'password',
|
||||
'signed_internship_form_path',
|
||||
'to_be_signed_internship_form_path',
|
||||
'internship_start_date',
|
||||
'internship_end_date',
|
||||
'internship_total_days',
|
||||
'github_repo',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class CompanyHistoryItem extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasTranslations;
|
||||
|
||||
public const QUARTERS = ['Q1', 'Q2', 'Q3', 'Q4'];
|
||||
|
||||
protected $fillable = [
|
||||
'year',
|
||||
'quarter',
|
||||
'title',
|
||||
'content',
|
||||
'color',
|
||||
'icon',
|
||||
'position',
|
||||
'is_active',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $translatable = [
|
||||
'title',
|
||||
'content',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'year' => 'integer',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query
|
||||
->orderBy('year', 'asc')
|
||||
->orderByRaw("CASE quarter WHEN 'Q1' THEN 1 WHEN 'Q2' THEN 2 WHEN 'Q3' THEN 3 WHEN 'Q4' THEN 4 ELSE 5 END")
|
||||
->orderBy('sort_order', 'asc');
|
||||
}
|
||||
|
||||
public function getResolvedPositionAttribute(): string
|
||||
{
|
||||
if (in_array($this->position, ['left', 'right'], true)) {
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
return ($this->sort_order % 2 === 1) ? 'right' : 'left';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Traits\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class MusicProduction extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, HasTranslations;
|
||||
|
||||
protected $table = 'music_productions';
|
||||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'slug',
|
||||
'spotify_album_id',
|
||||
'spotify_url',
|
||||
'spotify_cover_url',
|
||||
'spotify_type',
|
||||
'spotify_synced_at',
|
||||
'youtube_video_id',
|
||||
'youtube_url',
|
||||
'youtube_cover_url',
|
||||
'youtube_description',
|
||||
'youtube_synced_at',
|
||||
'cover_image',
|
||||
'content',
|
||||
'client_name',
|
||||
'production_date',
|
||||
'gallery',
|
||||
'is_active',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
/**
|
||||
* Translatable fields
|
||||
*/
|
||||
protected $translatable = [
|
||||
'title',
|
||||
'content',
|
||||
'client_name',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'production_date' => 'date',
|
||||
'spotify_synced_at' => 'datetime',
|
||||
'youtube_synced_at' => 'datetime',
|
||||
'gallery' => 'array',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
public function getUrlAttribute()
|
||||
{
|
||||
return route('music-productions.show', $this->slug);
|
||||
}
|
||||
|
||||
public function getCoverImageUrlAttribute()
|
||||
{
|
||||
if ($this->cover_image) {
|
||||
return asset('storage/' . $this->cover_image);
|
||||
}
|
||||
|
||||
if ($this->spotify_cover_url) {
|
||||
return $this->spotify_cover_url;
|
||||
}
|
||||
|
||||
if ($this->youtube_cover_url) {
|
||||
return $this->youtube_cover_url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getDisplayCoverImageAttribute(): ?string
|
||||
{
|
||||
if ($this->cover_image) {
|
||||
return $this->cover_image;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('is_active', true);
|
||||
}
|
||||
|
||||
public function scopeOrdered($query)
|
||||
{
|
||||
return $query->orderBy('sort_order', 'asc')->orderBy('production_date', 'desc');
|
||||
}
|
||||
}
|
||||
+8
-6
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\SectionTemplate;
|
||||
use App\Support\PageTemplateHero;
|
||||
use App\Traits\HasTranslations;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -115,13 +116,14 @@ class Page extends Model
|
||||
return '/' . $this->slug;
|
||||
}
|
||||
|
||||
public function getFeaturedImageUrlAttribute()
|
||||
public function getFeaturedImageUrlAttribute(): ?string
|
||||
{
|
||||
if ($this->featured_image) {
|
||||
return asset('storage/' . $this->featured_image);
|
||||
}
|
||||
|
||||
return null;
|
||||
return PageTemplateHero::urlForPage($this);
|
||||
}
|
||||
|
||||
public function usesTemplateHeroImage(): bool
|
||||
{
|
||||
return ! $this->featured_image && PageTemplateHero::hasHero($this->template);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Proposal extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'slug',
|
||||
'title',
|
||||
'client_name',
|
||||
'client_email',
|
||||
'content',
|
||||
'total_price',
|
||||
'currency',
|
||||
'status',
|
||||
'valid_until',
|
||||
'client_feedback',
|
||||
'accepted_name',
|
||||
'accepted_at',
|
||||
'views_count',
|
||||
'meta',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'valid_until' => 'date',
|
||||
'accepted_at' => 'datetime',
|
||||
'meta' => 'array',
|
||||
'views_count' => 'integer',
|
||||
'total_price' => 'decimal:2',
|
||||
];
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function ($proposal) {
|
||||
if (empty($proposal->uuid)) {
|
||||
$proposal->uuid = (string) Str::uuid();
|
||||
}
|
||||
if (empty($proposal->created_by) && auth()->check()) {
|
||||
$proposal->created_by = auth()->id();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user who created the proposal.
|
||||
*/
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public url for the proposal.
|
||||
*/
|
||||
public function getUrlAttribute()
|
||||
{
|
||||
return route('proposals.show', $this->slug);
|
||||
}
|
||||
}
|
||||
+135
-13
@@ -10,6 +10,17 @@ class Setting extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::saved(function ($setting) {
|
||||
\Illuminate\Support\Facades\Cache::forget("app_setting_{$setting->key}");
|
||||
});
|
||||
|
||||
static::deleted(function ($setting) {
|
||||
\Illuminate\Support\Facades\Cache::forget("app_setting_{$setting->key}");
|
||||
});
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'value',
|
||||
@@ -64,17 +75,115 @@ class Setting extends Model
|
||||
$this->attributes['value'] = $value ? '1' : '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value from different field names
|
||||
*/
|
||||
public function setValueFileAttribute($value)
|
||||
{
|
||||
// Filament bazen dosya yolunu array olarak döndürebilir, bu durumda ilk elemanı alıyoruz
|
||||
if (is_array($value)) {
|
||||
$this->attributes['value'] = array_values($value)[0] ?? null;
|
||||
$filePath = array_values($value)[0] ?? null;
|
||||
} else {
|
||||
$this->attributes['value'] = $value;
|
||||
$filePath = $value;
|
||||
}
|
||||
|
||||
if ($filePath && $this->key === 'hero_bg') {
|
||||
$filePath = $this->processHeroBackground($filePath);
|
||||
}
|
||||
|
||||
$this->attributes['value'] = $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the hero background image: convert to webp, resize if necessary, and compress.
|
||||
*/
|
||||
protected function processHeroBackground($filePath)
|
||||
{
|
||||
try {
|
||||
$disk = \Illuminate\Support\Facades\Storage::disk('public');
|
||||
if (!$disk->exists($filePath)) {
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
$absolutePath = $disk->path($filePath);
|
||||
|
||||
// Get image info
|
||||
$imageInfo = @getimagesize($absolutePath);
|
||||
if (!$imageInfo) {
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
$mimeType = $imageInfo['mime'];
|
||||
|
||||
// Create GD image resource based on mime type
|
||||
switch ($mimeType) {
|
||||
case 'image/jpeg':
|
||||
case 'image/jpg':
|
||||
$image = @imagecreatefromjpeg($absolutePath);
|
||||
break;
|
||||
case 'image/png':
|
||||
$image = @imagecreatefrompng($absolutePath);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$image = @imagecreatefromwebp($absolutePath);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$image = @imagecreatefromgif($absolutePath);
|
||||
break;
|
||||
default:
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
if (!$image) {
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
// Preserve transparency or handle it for webp if needed
|
||||
imagealphablending($image, true);
|
||||
imagesavealpha($image, true);
|
||||
|
||||
$originalWidth = imagesx($image);
|
||||
$originalHeight = imagesy($image);
|
||||
|
||||
// Target dimensions: Hero bg size, max width 1920px
|
||||
$maxWidth = 1920;
|
||||
if ($originalWidth > $maxWidth) {
|
||||
$newWidth = $maxWidth;
|
||||
$newHeight = (int) (($originalHeight / $originalWidth) * $maxWidth);
|
||||
|
||||
$resizedImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
// Preserve transparency
|
||||
imagealphablending($resizedImage, false);
|
||||
imagesavealpha($resizedImage, true);
|
||||
|
||||
// Resize
|
||||
imagecopyresampled($resizedImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $originalWidth, $originalHeight);
|
||||
imagedestroy($image);
|
||||
$image = $resizedImage;
|
||||
}
|
||||
|
||||
// Generate new webp path
|
||||
$pathInfo = pathinfo($filePath);
|
||||
$newWebpPath = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '.webp';
|
||||
$absoluteWebpPath = $disk->path($newWebpPath);
|
||||
|
||||
// Save as webp with 80% quality (excellent quality-to-size ratio)
|
||||
if (imagewebp($image, $absoluteWebpPath, 80)) {
|
||||
imagedestroy($image);
|
||||
|
||||
// If the file extension changed, delete the original file
|
||||
if ($newWebpPath !== $filePath) {
|
||||
$disk->delete($filePath);
|
||||
}
|
||||
|
||||
return $newWebpPath;
|
||||
}
|
||||
|
||||
imagedestroy($image);
|
||||
} catch (\Exception $e) {
|
||||
// Log error or fallback to original
|
||||
\Illuminate\Support\Facades\Log::error('Hero background image processing failed: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,15 +418,24 @@ class Setting extends Model
|
||||
return isset($this->attributes['value']) ? json_decode($this->attributes['value'], true) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an active setting record by key.
|
||||
*/
|
||||
public static function findActiveByKey(string $key): ?self
|
||||
{
|
||||
return static::query()
|
||||
->where('key', $key)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setting by key
|
||||
*/
|
||||
public static function get(string $key, $default = null)
|
||||
{
|
||||
$setting = static::where('key', $key)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
$setting = static::findActiveByKey($key);
|
||||
|
||||
if (!$setting) {
|
||||
return $default;
|
||||
}
|
||||
@@ -369,12 +487,16 @@ class Setting extends Model
|
||||
*/
|
||||
public static function getGroup(string $group): array
|
||||
{
|
||||
return static::where('group', $group)
|
||||
return static::query()
|
||||
->where('group', $group)
|
||||
->where('is_active', true)
|
||||
->pluck('value', 'key')
|
||||
->map(function ($value, $key) use ($group) {
|
||||
$setting = static::where('key', $key)->first();
|
||||
return static::castValue($value, $setting->type);
|
||||
->map(function ($value, $key) {
|
||||
$setting = static::findActiveByKey($key);
|
||||
|
||||
return $setting
|
||||
? static::castValue($value, $setting->type)
|
||||
: $value;
|
||||
})
|
||||
->toArray();
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ class User extends Authenticatable
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'avatar',
|
||||
'role',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SpotifyService
|
||||
{
|
||||
protected ?string $accessToken = null;
|
||||
|
||||
public function __construct(
|
||||
protected ?string $clientId = null,
|
||||
protected ?string $clientSecret = null,
|
||||
protected ?string $artistId = null,
|
||||
) {
|
||||
$this->clientId = $clientId ?? config('services.spotify.client_id');
|
||||
$this->clientSecret = $clientSecret ?? config('services.spotify.client_secret');
|
||||
$this->artistId = $artistId ?? config('services.spotify.artist_id');
|
||||
}
|
||||
|
||||
public function isConfigured(): bool
|
||||
{
|
||||
return filled($this->clientId)
|
||||
&& filled($this->clientSecret)
|
||||
&& filled($this->artistId);
|
||||
}
|
||||
|
||||
public function getArtistId(): ?string
|
||||
{
|
||||
return $this->artistId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getArtistAlbums(string $includeGroups = 'album,single'): array
|
||||
{
|
||||
$this->authenticate();
|
||||
|
||||
$albums = [];
|
||||
$url = "https://api.spotify.com/v1/artists/{$this->artistId}/albums";
|
||||
$params = [
|
||||
'include_groups' => $includeGroups,
|
||||
'limit' => 50,
|
||||
'market' => config('services.spotify.market', 'TR'),
|
||||
];
|
||||
|
||||
do {
|
||||
$response = $this->client()->get($url, $params);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('Spotify artist albums fetch failed', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
|
||||
throw new \RuntimeException($this->formatApiError(
|
||||
__('music_productions.spotify_albums_fetch_failed'),
|
||||
$response
|
||||
));
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$items = $data['items'] ?? [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$albums[$item['id']] = $item;
|
||||
}
|
||||
|
||||
$url = $data['next'] ?? null;
|
||||
$params = [];
|
||||
} while ($url);
|
||||
|
||||
return array_values($albums);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function getAlbum(string $albumId): ?array
|
||||
{
|
||||
$this->authenticate();
|
||||
|
||||
$response = $this->client()->get("https://api.spotify.com/v1/albums/{$albumId}", [
|
||||
'market' => config('services.spotify.market', 'TR'),
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::warning('Spotify album fetch failed', [
|
||||
'album_id' => $albumId,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $response->json();
|
||||
}
|
||||
|
||||
public function downloadCoverImage(?string $imageUrl, string $albumId): ?string
|
||||
{
|
||||
if (blank($imageUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(30)->get($imageUrl);
|
||||
|
||||
if ($response->failed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$extension = $this->guessImageExtension($response->header('Content-Type'));
|
||||
$path = 'music-productions/covers/spotify-' . $albumId . '.' . $extension;
|
||||
|
||||
Storage::disk('public')->put($path, $response->body());
|
||||
|
||||
return $path;
|
||||
} catch (\Throwable $exception) {
|
||||
Log::warning('Spotify cover download failed', [
|
||||
'album_id' => $albumId,
|
||||
'message' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function buildDefaultContent(array $album): string
|
||||
{
|
||||
$title = e($album['name'] ?? '');
|
||||
$spotifyUrl = e($album['external_urls']['spotify'] ?? '');
|
||||
$artistNames = collect($album['artists'] ?? [])
|
||||
->pluck('name')
|
||||
->filter()
|
||||
->implode(', ');
|
||||
$artistNames = e($artistNames);
|
||||
$albumType = e($album['album_type'] ?? 'album');
|
||||
$totalTracks = (int) ($album['total_tracks'] ?? 0);
|
||||
|
||||
$listenLabel = e(__('music_productions.spotify_listen_on_spotify'));
|
||||
|
||||
return <<<HTML
|
||||
<p><strong>{$title}</strong> — {$artistNames}</p>
|
||||
<p>{$albumType} · {$totalTracks} {$this->trackLabel($totalTracks)}</p>
|
||||
<p><a href="{$spotifyUrl}" target="_blank" rel="noopener noreferrer">{$listenLabel}</a></p>
|
||||
HTML;
|
||||
}
|
||||
|
||||
protected function trackLabel(int $count): string
|
||||
{
|
||||
return $count === 1
|
||||
? __('music_productions.spotify_track_singular')
|
||||
: __('music_productions.spotify_track_plural');
|
||||
}
|
||||
|
||||
protected function authenticate(): void
|
||||
{
|
||||
if ($this->accessToken) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->isConfigured()) {
|
||||
throw new \RuntimeException(__('music_productions.spotify_not_configured'));
|
||||
}
|
||||
|
||||
$response = Http::asForm()
|
||||
->withHeaders([
|
||||
'Authorization' => 'Basic ' . base64_encode($this->clientId . ':' . $this->clientSecret),
|
||||
])
|
||||
->post('https://accounts.spotify.com/api/token', [
|
||||
'grant_type' => 'client_credentials',
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('Spotify access token request failed', [
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
|
||||
throw new \RuntimeException($this->formatApiError(
|
||||
__('music_productions.spotify_token_failed'),
|
||||
$response
|
||||
));
|
||||
}
|
||||
|
||||
$this->accessToken = $response->json('access_token');
|
||||
}
|
||||
|
||||
protected function client(): PendingRequest
|
||||
{
|
||||
return Http::withToken($this->accessToken)
|
||||
->acceptJson()
|
||||
->timeout(30);
|
||||
}
|
||||
|
||||
protected function formatApiError(string $fallback, \Illuminate\Http\Client\Response $response): string
|
||||
{
|
||||
$body = trim($response->body());
|
||||
$message = $body;
|
||||
|
||||
if (str_starts_with($body, '{')) {
|
||||
$message = $response->json('error.message')
|
||||
?? $response->json('error_description')
|
||||
?? $body;
|
||||
}
|
||||
|
||||
if (blank($message)) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
if ($response->status() === 403 && str_contains(strtolower($message), 'premium')) {
|
||||
return __('music_productions.spotify_premium_required', ['detail' => $message]);
|
||||
}
|
||||
|
||||
return $fallback . ' (HTTP ' . $response->status() . ': ' . $message . ')';
|
||||
}
|
||||
|
||||
protected function guessImageExtension(?string $contentType): string
|
||||
{
|
||||
return match ($contentType) {
|
||||
'image/png' => 'png',
|
||||
'image/webp' => 'webp',
|
||||
'image/gif' => 'gif',
|
||||
default => 'jpg',
|
||||
};
|
||||
}
|
||||
|
||||
public static function uniqueSlug(string $title, ?int $ignoreId = null): string
|
||||
{
|
||||
$baseSlug = Str::slug($title) ?: 'spotify-album';
|
||||
$slug = $baseSlug;
|
||||
$counter = 1;
|
||||
|
||||
while (
|
||||
\App\Models\MusicProduction::withTrashed()
|
||||
->when($ignoreId, fn ($query) => $query->where('id', '!=', $ignoreId))
|
||||
->where('slug', $slug)
|
||||
->exists()
|
||||
) {
|
||||
$slug = $baseSlug . '-' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,10 @@ namespace App\Services;
|
||||
use App\Models\HeaderTemplate;
|
||||
use App\Models\SectionTemplate;
|
||||
use App\Models\FooterTemplate;
|
||||
use App\Models\Setting;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Filament\Forms\Components\{
|
||||
TextInput, Textarea, Select, Checkbox, CheckboxList,
|
||||
Radio, Toggle, ToggleButtons, DateTimePicker, DatePicker,
|
||||
@@ -294,11 +297,15 @@ class TemplateService
|
||||
*/
|
||||
protected static function getSelectOptions(string $fieldName): array
|
||||
{
|
||||
return config("template-options.{$fieldName}", [
|
||||
$defaults = [
|
||||
'option_1' => __('Option 1'),
|
||||
'option_2' => __('Option 2'),
|
||||
'option_3' => __('Option 3'),
|
||||
]);
|
||||
];
|
||||
|
||||
$options = config("template-options.{$fieldName}", $defaults);
|
||||
|
||||
return is_array($options) ? $options : $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,6 +383,15 @@ class TemplateService
|
||||
}
|
||||
|
||||
// Handle custom blade components: {custom.component_name} or {custom.component-name}
|
||||
if (str_contains($html, '{custom.language-selector}')) {
|
||||
$desktopLanguage = view('components.custom.language-selector', [
|
||||
'variant' => 'default',
|
||||
])->render();
|
||||
|
||||
$html = str_replace('{custom.language-selector}', $desktopLanguage, $html);
|
||||
$html = self::injectOffcanvasLanguageSelector($html);
|
||||
}
|
||||
|
||||
preg_match_all('/\{custom\.([a-z][a-z_-]*)\}/i', $html, $customMatches);
|
||||
if (!empty($customMatches[0])) {
|
||||
foreach ($customMatches[0] as $index => $fullMatch) {
|
||||
@@ -383,8 +399,12 @@ class TemplateService
|
||||
if ($componentName) {
|
||||
// Normalize component name to lowercase for consistent file system access
|
||||
$componentName = strtolower($componentName);
|
||||
|
||||
if ($componentName === 'language-selector') {
|
||||
continue;
|
||||
}
|
||||
$viewPath = "components.custom.{$componentName}";
|
||||
if (view()->exists($viewPath)) {
|
||||
if (View::exists($viewPath)) {
|
||||
try {
|
||||
$renderedComponent = view($viewPath)->render();
|
||||
// Ensure rendered component is properly formatted
|
||||
@@ -392,7 +412,7 @@ class TemplateService
|
||||
$html = str_replace($fullMatch, $renderedComponent, $html);
|
||||
} catch (\Exception $e) {
|
||||
// Log the error for debugging
|
||||
\Log::error("Custom component render error: {$viewPath}", [
|
||||
Log::error("Custom component render error: {$viewPath}", [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
@@ -502,10 +522,10 @@ class TemplateService
|
||||
// Handle setting.* placeholders - get value from Setting model
|
||||
if ($type === 'setting' && $field) {
|
||||
$value = setting($field, '');
|
||||
|
||||
|
||||
// Get setting type to determine if it's a file/image
|
||||
$settingModel = \App\Models\Setting::where('key', $field)->where('is_active', true)->first();
|
||||
if ($settingModel && in_array($settingModel->type, ['file', 'image'])) {
|
||||
$settingModel = Setting::findActiveByKey($field);
|
||||
if ($settingModel && in_array($settingModel->type, ['file', 'image'], true)) {
|
||||
// For file/image settings, use file type for formatting
|
||||
$type = 'file';
|
||||
}
|
||||
@@ -660,5 +680,32 @@ class TemplateService
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject mobile language selector at the top of the first offcanvas menu body.
|
||||
*/
|
||||
protected static function injectOffcanvasLanguageSelector(string $html): string
|
||||
{
|
||||
if (str_contains($html, 'offcanvas-language-mobile-wrap')) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$mobileLanguage = view('components.custom.language-selector', [
|
||||
'variant' => 'offcanvas',
|
||||
'class' => 'offcanvas-language-mobile',
|
||||
])->render();
|
||||
|
||||
$injection = '<div class="xl:!hidden lg:!hidden pb-4 mb-2 border-b border-white/10 offcanvas-language-mobile-wrap">'
|
||||
. $mobileLanguage
|
||||
. '</div>';
|
||||
|
||||
$pattern = '/(<div[^>]*class="[^"]*offcanvas-body[^"]*"[^>]*>)/i';
|
||||
|
||||
if (preg_match($pattern, $html)) {
|
||||
return preg_replace($pattern, '$1' . $injection, $html, 1);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,489 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class YouTubeService
|
||||
{
|
||||
protected ?string $resolvedTopicChannelId = null;
|
||||
|
||||
protected ?string $resolvedReleasesSource = null;
|
||||
|
||||
public function __construct(
|
||||
protected ?string $apiKey = null,
|
||||
protected ?string $channelId = null,
|
||||
protected ?string $topicChannelId = null,
|
||||
protected ?string $playlistId = null,
|
||||
) {
|
||||
$this->apiKey = $apiKey ?? config('services.youtube.api_key');
|
||||
$this->channelId = $channelId ?? config('services.youtube.channel_id');
|
||||
$this->topicChannelId = $topicChannelId ?? config('services.youtube.topic_channel_id');
|
||||
$this->playlistId = $playlistId ?? config('services.youtube.playlist_id');
|
||||
}
|
||||
|
||||
public function isConfigured(): bool
|
||||
{
|
||||
return filled($this->apiKey)
|
||||
&& (filled($this->channelId) || filled($this->topicChannelId) || filled($this->playlistId));
|
||||
}
|
||||
|
||||
public function getReleasesSourceLabel(): string
|
||||
{
|
||||
$this->resolveReleasesPlaylistId();
|
||||
|
||||
return $this->resolvedReleasesSource ?? '-';
|
||||
}
|
||||
|
||||
public function resolveReleasesPlaylistId(): ?string
|
||||
{
|
||||
if (filled($this->playlistId)) {
|
||||
$this->resolvedReleasesSource = 'playlist:' . $this->playlistId;
|
||||
|
||||
return $this->playlistId;
|
||||
}
|
||||
|
||||
$topicChannelId = $this->resolveTopicChannelId();
|
||||
|
||||
if (filled($topicChannelId)) {
|
||||
$playlistId = $this->channelToUploadsPlaylistId($topicChannelId);
|
||||
$this->resolvedReleasesSource = 'topic:' . $topicChannelId;
|
||||
|
||||
return $playlistId;
|
||||
}
|
||||
|
||||
if (filled($this->channelId)) {
|
||||
Log::warning('YouTube Topic channel not found, falling back to main channel uploads', [
|
||||
'channel_id' => $this->channelId,
|
||||
]);
|
||||
|
||||
$playlistId = $this->channelToUploadsPlaylistId($this->channelId);
|
||||
$this->resolvedReleasesSource = 'main:' . $this->channelId;
|
||||
|
||||
return $playlistId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function resolveTopicChannelId(): ?string
|
||||
{
|
||||
if ($this->resolvedTopicChannelId !== null) {
|
||||
return $this->resolvedTopicChannelId ?: null;
|
||||
}
|
||||
|
||||
if (filled($this->topicChannelId)) {
|
||||
$this->resolvedTopicChannelId = $this->topicChannelId;
|
||||
|
||||
return $this->resolvedTopicChannelId;
|
||||
}
|
||||
|
||||
if (blank($this->channelId)) {
|
||||
$this->resolvedTopicChannelId = '';
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->resolvedTopicChannelId = $this->discoverTopicChannelId($this->channelId) ?? '';
|
||||
|
||||
return $this->resolvedTopicChannelId ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function getReleases(): array
|
||||
{
|
||||
$playlistId = $this->resolveReleasesPlaylistId();
|
||||
|
||||
if (blank($playlistId)) {
|
||||
throw new \RuntimeException(__('music_productions.youtube_playlist_not_resolved'));
|
||||
}
|
||||
|
||||
$releases = [];
|
||||
$pageToken = null;
|
||||
$distributorFilter = config('services.youtube.distributor_filter');
|
||||
|
||||
do {
|
||||
$params = [
|
||||
'key' => $this->apiKey,
|
||||
'playlistId' => $playlistId,
|
||||
'part' => 'snippet',
|
||||
'maxResults' => 50,
|
||||
];
|
||||
|
||||
if ($pageToken) {
|
||||
$params['pageToken'] = $pageToken;
|
||||
}
|
||||
|
||||
$response = $this->client()->get('https://www.googleapis.com/youtube/v3/playlistItems', $params);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('YouTube releases fetch failed', [
|
||||
'playlist_id' => $playlistId,
|
||||
'source' => $this->resolvedReleasesSource,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
|
||||
throw new \RuntimeException($this->formatApiError(
|
||||
__('music_productions.youtube_releases_fetch_failed'),
|
||||
$response
|
||||
));
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
$items = $data['items'] ?? [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$normalized = $this->normalizeReleaseItem($item, $distributorFilter);
|
||||
|
||||
if ($normalized) {
|
||||
$releases[$normalized['video_id']] = $normalized;
|
||||
}
|
||||
}
|
||||
|
||||
$pageToken = $data['nextPageToken'] ?? null;
|
||||
} while ($pageToken);
|
||||
|
||||
return array_values($releases);
|
||||
}
|
||||
|
||||
protected function discoverTopicChannelId(string $mainChannelId): ?string
|
||||
{
|
||||
$channel = $this->getChannel($mainChannelId);
|
||||
|
||||
if (! $channel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$artistTitle = trim($channel['snippet']['title'] ?? '');
|
||||
|
||||
if (blank($artistTitle)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expectedTopicTitle = $artistTitle . ' - Topic';
|
||||
$mainThumbnail = $channel['snippet']['thumbnails']['default']['url'] ?? null;
|
||||
|
||||
$response = $this->client()->get('https://www.googleapis.com/youtube/v3/search', [
|
||||
'key' => $this->apiKey,
|
||||
'part' => 'snippet',
|
||||
'type' => 'channel',
|
||||
'q' => $expectedTopicTitle,
|
||||
'maxResults' => 10,
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::warning('YouTube topic channel search failed', [
|
||||
'channel_id' => $mainChannelId,
|
||||
'query' => $expectedTopicTitle,
|
||||
'status' => $response->status(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$topicCandidates = [];
|
||||
|
||||
foreach ($response->json('items') ?? [] as $item) {
|
||||
$channelId = $item['snippet']['channelId'] ?? $item['id']['channelId'] ?? null;
|
||||
$title = trim($item['snippet']['title'] ?? '');
|
||||
|
||||
if (blank($channelId) || $channelId === $mainChannelId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->titlesMatch($title, $expectedTopicTitle)) {
|
||||
Log::info('YouTube topic channel resolved by exact title match', [
|
||||
'main_channel_id' => $mainChannelId,
|
||||
'topic_channel_id' => $channelId,
|
||||
'title' => $title,
|
||||
]);
|
||||
|
||||
return $channelId;
|
||||
}
|
||||
|
||||
if (str_ends_with($title, ' - Topic')) {
|
||||
$topicCandidates[] = [
|
||||
'channel_id' => $channelId,
|
||||
'title' => $title,
|
||||
'thumbnail' => $item['snippet']['thumbnails']['default']['url'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($topicCandidates as $candidate) {
|
||||
if ($this->thumbnailsMatch($mainThumbnail, $candidate['thumbnail'])) {
|
||||
Log::info('YouTube topic channel resolved by avatar match', [
|
||||
'main_channel_id' => $mainChannelId,
|
||||
'topic_channel_id' => $candidate['channel_id'],
|
||||
'title' => $candidate['title'],
|
||||
]);
|
||||
|
||||
return $candidate['channel_id'];
|
||||
}
|
||||
}
|
||||
|
||||
Log::warning('YouTube topic channel could not be resolved safely', [
|
||||
'main_channel_id' => $mainChannelId,
|
||||
'expected_title' => $expectedTopicTitle,
|
||||
'candidates' => collect($topicCandidates)->pluck('title')->all(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function titlesMatch(string $left, string $right): bool
|
||||
{
|
||||
return mb_strtolower(trim($left)) === mb_strtolower(trim($right));
|
||||
}
|
||||
|
||||
protected function thumbnailsMatch(?string $left, ?string $right): bool
|
||||
{
|
||||
if (blank($left) || blank($right)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->normalizeThumbnailUrl($left) === $this->normalizeThumbnailUrl($right);
|
||||
}
|
||||
|
||||
protected function normalizeThumbnailUrl(string $url): string
|
||||
{
|
||||
$path = parse_url($url, PHP_URL_PATH) ?: $url;
|
||||
|
||||
return preg_replace('/=s\d+-/', '=s88-', $path) ?? $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
protected function getChannel(string $channelId): ?array
|
||||
{
|
||||
$response = $this->client()->get('https://www.googleapis.com/youtube/v3/channels', [
|
||||
'key' => $this->apiKey,
|
||||
'id' => $channelId,
|
||||
'part' => 'snippet,contentDetails',
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $response->json('items.0');
|
||||
}
|
||||
|
||||
protected function channelToUploadsPlaylistId(string $channelId): string
|
||||
{
|
||||
if (str_starts_with($channelId, 'UU') || str_starts_with($channelId, 'PL')) {
|
||||
return $channelId;
|
||||
}
|
||||
|
||||
if (str_starts_with($channelId, 'UC')) {
|
||||
return 'UU' . substr($channelId, 2);
|
||||
}
|
||||
|
||||
return $channelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $item
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
protected function normalizeReleaseItem(array $item, ?string $distributorFilter = null): ?array
|
||||
{
|
||||
$snippet = $item['snippet'] ?? [];
|
||||
$videoId = $snippet['resourceId']['videoId'] ?? null;
|
||||
$title = trim($snippet['title'] ?? '');
|
||||
$description = trim($snippet['description'] ?? '');
|
||||
|
||||
if (blank($videoId) || blank($title)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (in_array($title, ['Private video', 'Deleted video', 'Gizli video', 'Silinmiş video'], true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (filled($distributorFilter) && ! str_contains($description, $distributorFilter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$thumbnails = $snippet['thumbnails'] ?? [];
|
||||
$coverUrl = $thumbnails['maxres']['url']
|
||||
?? $thumbnails['standard']['url']
|
||||
?? $thumbnails['high']['url']
|
||||
?? $thumbnails['medium']['url']
|
||||
?? $thumbnails['default']['url']
|
||||
?? null;
|
||||
|
||||
return [
|
||||
'video_id' => $videoId,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'cover_url' => $coverUrl,
|
||||
'published_at' => $snippet['publishedAt'] ?? null,
|
||||
'release_date' => $this->parseReleaseDate($description, $snippet['publishedAt'] ?? null),
|
||||
'youtube_url' => 'https://www.youtube.com/watch?v=' . $videoId,
|
||||
];
|
||||
}
|
||||
|
||||
public function parseReleaseDate(string $description, ?string $publishedAt = null): ?string
|
||||
{
|
||||
if (preg_match('/Released on:\s*(\d{4}-\d{2}-\d{2})/i', $description, $matches)) {
|
||||
return Carbon::parse($matches[1])->toDateString();
|
||||
}
|
||||
|
||||
if (preg_match('/Release date:\s*(\d{4}-\d{2}-\d{2})/i', $description, $matches)) {
|
||||
return Carbon::parse($matches[1])->toDateString();
|
||||
}
|
||||
|
||||
if (filled($publishedAt)) {
|
||||
return Carbon::parse($publishedAt)->toDateString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function downloadCoverImage(?string $imageUrl, string $videoId): ?string
|
||||
{
|
||||
if (blank($imageUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::timeout(30)->get($imageUrl);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::warning('YouTube cover image fetch HTTP request failed', [
|
||||
'video_id' => $videoId,
|
||||
'url' => $imageUrl,
|
||||
'status' => $response->status(),
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$extension = $this->guessImageExtension($response->header('Content-Type'));
|
||||
$path = 'music-productions/covers/youtube-' . $videoId . '.' . $extension;
|
||||
|
||||
$absolutePath = Storage::disk('public')->path($path);
|
||||
$directory = dirname($absolutePath);
|
||||
|
||||
if (!is_dir($directory)) {
|
||||
@mkdir($directory, 0775, true);
|
||||
}
|
||||
|
||||
if (is_dir($directory) && !is_writable($directory)) {
|
||||
Log::error('YouTube cover directory is not writable. Check folder ownership and permissions.', [
|
||||
'directory' => $directory,
|
||||
'owner' => function_exists('posix_getpwuid') ? posix_getpwuid(fileowner($directory))['name'] : fileowner($directory),
|
||||
'perms' => substr(sprintf('%o', fileperms($directory)), -4),
|
||||
]);
|
||||
}
|
||||
|
||||
$stored = Storage::disk('public')->put($path, $response->body());
|
||||
|
||||
if (!$stored) {
|
||||
Log::error('Failed to store YouTube cover image via storage disk.', [
|
||||
'path' => $path,
|
||||
'video_id' => $videoId,
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $path;
|
||||
} catch (\Throwable $exception) {
|
||||
Log::error('YouTube cover download failed with exception', [
|
||||
'video_id' => $videoId,
|
||||
'message' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function buildDefaultContent(array $release): string
|
||||
{
|
||||
$title = e($release['title'] ?? '');
|
||||
$youtubeUrl = e($release['youtube_url'] ?? '');
|
||||
$description = trim($release['description'] ?? '');
|
||||
$listenLabel = e(__('music_productions.youtube_watch_on_youtube'));
|
||||
|
||||
$descriptionHtml = filled($description)
|
||||
? '<p>' . nl2br(e($description)) . '</p>'
|
||||
: '';
|
||||
|
||||
return <<<HTML
|
||||
<p><strong>{$title}</strong></p>
|
||||
{$descriptionHtml}
|
||||
<p><a href="{$youtubeUrl}" target="_blank" rel="noopener noreferrer">{$listenLabel}</a></p>
|
||||
HTML;
|
||||
}
|
||||
|
||||
protected function client(): PendingRequest
|
||||
{
|
||||
$request = Http::acceptJson()->timeout(30);
|
||||
|
||||
$referer = config('services.youtube.api_referer');
|
||||
|
||||
if (filled($referer)) {
|
||||
$request = $request->withHeaders([
|
||||
'Referer' => rtrim($referer, '/') . '/',
|
||||
]);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
protected function formatApiError(string $fallback, \Illuminate\Http\Client\Response $response): string
|
||||
{
|
||||
$message = $response->json('error.message') ?? trim($response->body());
|
||||
|
||||
if (blank($message)) {
|
||||
return $fallback;
|
||||
}
|
||||
|
||||
if ($response->status() === 403 && str_contains(strtolower($message), 'referer')) {
|
||||
return __('music_productions.youtube_referrer_blocked', [
|
||||
'detail' => $message,
|
||||
]);
|
||||
}
|
||||
|
||||
return $fallback . ' (HTTP ' . $response->status() . ': ' . $message . ')';
|
||||
}
|
||||
|
||||
protected function guessImageExtension(?string $contentType): string
|
||||
{
|
||||
return match ($contentType) {
|
||||
'image/png' => 'png',
|
||||
'image/webp' => 'webp',
|
||||
'image/gif' => 'gif',
|
||||
default => 'jpg',
|
||||
};
|
||||
}
|
||||
|
||||
public static function uniqueSlug(string $title, ?int $ignoreId = null): string
|
||||
{
|
||||
$baseSlug = Str::slug($title) ?: 'youtube-release';
|
||||
$slug = $baseSlug;
|
||||
$counter = 1;
|
||||
|
||||
while (
|
||||
\App\Models\MusicProduction::withTrashed()
|
||||
->when($ignoreId, fn ($query) => $query->where('id', '!=', $ignoreId))
|
||||
->where('slug', $slug)
|
||||
->exists()
|
||||
) {
|
||||
$slug = $baseSlug . '-' . $counter;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\Blog;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BlogStructuredData extends StructuredData
|
||||
{
|
||||
public static function forIndex(LengthAwarePaginator $posts, string $pageUrl): array
|
||||
{
|
||||
$pageName = __('blog.meta-index-title');
|
||||
$pageDescription = __('blog.meta-index-description');
|
||||
|
||||
$itemListElements = [];
|
||||
$offset = ($posts->currentPage() - 1) * $posts->perPage();
|
||||
|
||||
foreach ($posts as $index => $post) {
|
||||
$itemListElements[] = self::listItem(
|
||||
$offset + $index + 1,
|
||||
self::postSummarySchema($post),
|
||||
);
|
||||
}
|
||||
|
||||
return self::wrap([
|
||||
self::websiteNode(),
|
||||
self::organizationNode(),
|
||||
self::webPageNode($pageUrl, $pageName, $pageDescription, [
|
||||
'@type' => 'CollectionPage',
|
||||
'mainEntity' => ['@id' => $pageUrl . '#itemlist'],
|
||||
]),
|
||||
self::breadcrumbNode($pageUrl, [
|
||||
['name' => __('blog.breadcrumb_home'), 'item' => self::siteUrl()],
|
||||
['name' => $pageName, 'item' => $pageUrl],
|
||||
]),
|
||||
[
|
||||
'@type' => 'ItemList',
|
||||
'@id' => $pageUrl . '#itemlist',
|
||||
'name' => $pageName,
|
||||
'numberOfItems' => $posts->total(),
|
||||
'itemListElement' => $itemListElements,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function forShow(Blog $post, string $pageUrl): array
|
||||
{
|
||||
$title = $post->translate('title');
|
||||
$description = Str::limit(strip_tags((string) $post->translate('excerpt')), 160);
|
||||
$imageUrl = $post->featured_image_url ?: self::defaultImageUrl();
|
||||
$publishedAt = $post->published_at ?? $post->created_at;
|
||||
|
||||
$blogPosting = [
|
||||
'@type' => 'BlogPosting',
|
||||
'@id' => $pageUrl . '#article',
|
||||
'mainEntityOfPage' => ['@id' => $pageUrl . '#webpage'],
|
||||
'headline' => $title,
|
||||
'description' => $description,
|
||||
'image' => [$imageUrl],
|
||||
'inLanguage' => app()->getLocale(),
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => $post->author->name ?? __('blog.default_author'),
|
||||
],
|
||||
'publisher' => self::publisherNode(),
|
||||
'datePublished' => $publishedAt->toIso8601String(),
|
||||
'dateModified' => $post->updated_at->toIso8601String(),
|
||||
'url' => $pageUrl,
|
||||
];
|
||||
|
||||
$content = strip_tags((string) $post->translate('content'));
|
||||
if ($content !== '') {
|
||||
$blogPosting['articleBody'] = Str::limit($content, 5000);
|
||||
}
|
||||
|
||||
return self::wrap([
|
||||
self::websiteNode(),
|
||||
self::organizationNode(),
|
||||
self::webPageNode($pageUrl, $title, $description, [
|
||||
'mainEntity' => ['@id' => $pageUrl . '#article'],
|
||||
'dateModified' => $post->updated_at->toIso8601String(),
|
||||
]),
|
||||
self::breadcrumbNode($pageUrl, [
|
||||
['name' => __('blog.breadcrumb_home'), 'item' => self::siteUrl()],
|
||||
['name' => __('blog.meta-index-title'), 'item' => route('blog.index')],
|
||||
['name' => $title, 'item' => $pageUrl],
|
||||
]),
|
||||
$blogPosting,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function postSummarySchema(Blog $post): array
|
||||
{
|
||||
$schema = [
|
||||
'@type' => 'BlogPosting',
|
||||
'headline' => $post->translate('title'),
|
||||
'url' => route('blog.show', $post->slug),
|
||||
];
|
||||
|
||||
if ($post->featured_image_url) {
|
||||
$schema['image'] = [$post->featured_image_url];
|
||||
}
|
||||
|
||||
$publishedAt = $post->published_at ?? $post->created_at;
|
||||
if ($publishedAt) {
|
||||
$schema['datePublished'] = $publishedAt->toIso8601String();
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function publisherNode(): array
|
||||
{
|
||||
$publisher = [
|
||||
'@type' => 'Organization',
|
||||
'@id' => self::siteUrl() . '#organization',
|
||||
'name' => self::siteName(),
|
||||
];
|
||||
|
||||
$logo = setting('site_logo');
|
||||
if ($logo) {
|
||||
$logoUrl = str_starts_with($logo, 'http') ? $logo : asset('storage/' . ltrim($logo, '/'));
|
||||
$publisher['logo'] = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $logoUrl,
|
||||
];
|
||||
}
|
||||
|
||||
return $publisher;
|
||||
}
|
||||
|
||||
protected static function defaultImageUrl(): string
|
||||
{
|
||||
$defaultImage = setting('default_meta_image');
|
||||
|
||||
if ($defaultImage) {
|
||||
return str_starts_with($defaultImage, 'http') ? $defaultImage : asset($defaultImage);
|
||||
}
|
||||
|
||||
return asset('assets/img/logo.png');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\MusicProduction;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class MusicProductionStructuredData extends StructuredData
|
||||
{
|
||||
public static function forIndex(LengthAwarePaginator $productions, string $pageUrl): array
|
||||
{
|
||||
$pageName = __('music_productions.meta-index-title');
|
||||
$pageDescription = __('music_productions.meta-index-description');
|
||||
|
||||
$itemListElements = [];
|
||||
$offset = ($productions->currentPage() - 1) * $productions->perPage();
|
||||
|
||||
foreach ($productions as $index => $production) {
|
||||
$itemListElements[] = self::listItem(
|
||||
$offset + $index + 1,
|
||||
self::productionSchema($production),
|
||||
);
|
||||
}
|
||||
|
||||
return self::wrap([
|
||||
self::websiteNode(route('music-productions.index') . '?q={search_term_string}'),
|
||||
self::organizationNode(),
|
||||
self::webPageNode($pageUrl, $pageName, $pageDescription, [
|
||||
'@type' => 'CollectionPage',
|
||||
'mainEntity' => ['@id' => $pageUrl . '#itemlist'],
|
||||
]),
|
||||
self::breadcrumbNode($pageUrl, [
|
||||
['name' => __('music_productions.breadcrumb_home'), 'item' => self::siteUrl()],
|
||||
['name' => __('music_productions.nav-music-productions'), 'item' => route('music-productions.index')],
|
||||
]),
|
||||
[
|
||||
'@type' => 'ItemList',
|
||||
'@id' => $pageUrl . '#itemlist',
|
||||
'name' => $pageName,
|
||||
'numberOfItems' => $productions->total(),
|
||||
'itemListElement' => $itemListElements,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function forShow(MusicProduction $production, string $pageUrl): array
|
||||
{
|
||||
$title = $production->translate('title');
|
||||
$description = Str::limit(strip_tags((string) $production->translate('content')), 160);
|
||||
|
||||
$webPageExtra = [
|
||||
'mainEntity' => ['@id' => $pageUrl . '#production'],
|
||||
];
|
||||
|
||||
if ($production->updated_at) {
|
||||
$webPageExtra['dateModified'] = $production->updated_at->toIso8601String();
|
||||
}
|
||||
|
||||
return self::wrap([
|
||||
self::websiteNode(route('music-productions.index') . '?q={search_term_string}'),
|
||||
self::organizationNode(),
|
||||
self::webPageNode($pageUrl, $title, $description, $webPageExtra),
|
||||
self::breadcrumbNode($pageUrl, [
|
||||
['name' => __('music_productions.breadcrumb_home'), 'item' => self::siteUrl()],
|
||||
['name' => __('music_productions.nav-music-productions'), 'item' => route('music-productions.index')],
|
||||
['name' => $title, 'item' => $pageUrl],
|
||||
]),
|
||||
array_merge(
|
||||
['@id' => $pageUrl . '#production'],
|
||||
self::productionSchema($production),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function productionSchema(MusicProduction $production): array
|
||||
{
|
||||
$schema = [
|
||||
'@type' => filled($production->spotify_album_id) ? 'MusicAlbum' : 'CreativeWork',
|
||||
'name' => $production->translate('title'),
|
||||
'url' => route('music-productions.show', $production->slug),
|
||||
];
|
||||
|
||||
if ($production->cover_image_url) {
|
||||
$schema['image'] = [$production->cover_image_url];
|
||||
}
|
||||
|
||||
$content = strip_tags((string) $production->translate('content'));
|
||||
if ($content !== '') {
|
||||
$schema['description'] = Str::limit($content, 300);
|
||||
}
|
||||
|
||||
if ($production->production_date) {
|
||||
$schema['datePublished'] = $production->production_date->toIso8601String();
|
||||
}
|
||||
|
||||
$clientName = $production->translate('client_name');
|
||||
if ($clientName) {
|
||||
$schema['creator'] = [
|
||||
'@type' => 'Organization',
|
||||
'name' => $clientName,
|
||||
];
|
||||
}
|
||||
|
||||
$sameAs = array_values(array_filter([
|
||||
$production->spotify_url,
|
||||
$production->youtube_url,
|
||||
]));
|
||||
|
||||
if ($sameAs !== []) {
|
||||
$schema['sameAs'] = $sameAs;
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\Page;
|
||||
|
||||
class PageStructuredData extends StructuredData
|
||||
{
|
||||
public static function forPage(?Page $page, string $pageUrl, ?string $name = null, ?string $description = null): array
|
||||
{
|
||||
$pageName = $name ?: ($page
|
||||
? (method_exists($page, 'translate')
|
||||
? ($page->translate('meta_title') ?: $page->translate('title'))
|
||||
: ($page->meta_title ?? $page->title ?? self::siteName()))
|
||||
: self::siteName());
|
||||
|
||||
$pageDescription = $description ?: ($page
|
||||
? (method_exists($page, 'translate')
|
||||
? ($page->translate('meta_description') ?: ($page->excerpt ?? null))
|
||||
: ($page->meta_description ?? $page->excerpt ?? null))
|
||||
: setting('seo_meta_description'));
|
||||
|
||||
$webPageExtra = [];
|
||||
|
||||
if ($page?->updated_at) {
|
||||
$webPageExtra['dateModified'] = $page->updated_at->toIso8601String();
|
||||
}
|
||||
|
||||
if ($page?->is_homepage) {
|
||||
$webPageExtra['@type'] = 'WebPage';
|
||||
}
|
||||
|
||||
$graph = [
|
||||
self::websiteNode(),
|
||||
self::organizationNode(),
|
||||
self::webPageNode($pageUrl, $pageName, $pageDescription, $webPageExtra),
|
||||
self::breadcrumbNode($pageUrl, self::breadcrumbItems($page, $pageUrl, $pageName)),
|
||||
];
|
||||
|
||||
return self::wrap($graph);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array{name: string, item: string}>
|
||||
*/
|
||||
protected static function breadcrumbItems(?Page $page, string $pageUrl, string $pageName): array
|
||||
{
|
||||
$items = [
|
||||
['name' => __('pages.breadcrumb_home'), 'item' => self::siteUrl()],
|
||||
];
|
||||
|
||||
if ($page && !$page->is_homepage) {
|
||||
$items[] = ['name' => $pageName, 'item' => $pageUrl];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\Page;
|
||||
|
||||
class PageTemplateHero
|
||||
{
|
||||
public static function config(?string $template): ?array
|
||||
{
|
||||
if (! filled($template)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$heroes = config('page_templates.heroes', []);
|
||||
|
||||
return $heroes[$template] ?? null;
|
||||
}
|
||||
|
||||
public static function hasHero(?string $template): bool
|
||||
{
|
||||
return filled(static::imagePath($template));
|
||||
}
|
||||
|
||||
public static function imagePath(?string $template): ?string
|
||||
{
|
||||
return static::config($template)['image'] ?? null;
|
||||
}
|
||||
|
||||
public static function webpPath(?string $template): ?string
|
||||
{
|
||||
return static::config($template)['webp'] ?? null;
|
||||
}
|
||||
|
||||
public static function url(?string $template): ?string
|
||||
{
|
||||
$path = static::imagePath($template);
|
||||
|
||||
return $path ? asset($path) : null;
|
||||
}
|
||||
|
||||
public static function webpUrl(?string $template): ?string
|
||||
{
|
||||
$path = static::webpPath($template);
|
||||
|
||||
return $path ? asset($path) : null;
|
||||
}
|
||||
|
||||
public static function featuredStoragePath(Page $page): ?string
|
||||
{
|
||||
$value = $page->featured_image;
|
||||
|
||||
if (blank($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$value = $value[0] ?? null;
|
||||
}
|
||||
|
||||
return is_string($value) && $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
public static function urlForPage(Page $page): ?string
|
||||
{
|
||||
if ($path = static::featuredStoragePath($page)) {
|
||||
return asset('storage/' . $path);
|
||||
}
|
||||
|
||||
return static::url($page->template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hero alanı için görsel verisi (yüklenen öne çıkan görsel veya şablon varsayılanı).
|
||||
*
|
||||
* @return array{image: string, webp?: string, width?: int, height?: int, from_upload: bool}|null
|
||||
*/
|
||||
public static function resolveForPage(?Page $page, ?string $templateFallback = null): ?array
|
||||
{
|
||||
if ($page && ($path = static::featuredStoragePath($page))) {
|
||||
return [
|
||||
'image' => asset('storage/' . $path),
|
||||
'from_upload' => true,
|
||||
];
|
||||
}
|
||||
|
||||
$template = $page?->template ?? $templateFallback;
|
||||
$config = static::config($template);
|
||||
|
||||
if (! $config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_merge($config, ['from_upload' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function templateFormOptions(): array
|
||||
{
|
||||
$base = [
|
||||
'default' => __('pages.template_default'),
|
||||
'landing' => __('pages.template_landing'),
|
||||
'blog' => __('pages.template_blog'),
|
||||
'contact' => __('pages.template_contact'),
|
||||
'home' => 'Home',
|
||||
'corporate.testimonials' => 'Müşteri Görüşleri (Kurumsal)',
|
||||
'corporate.logos' => 'Logolarımız (Kurumsal)',
|
||||
'corporate.partners' => 'Çözüm Ortaklarımız (Kurumsal)',
|
||||
'corporate.bank-accounts' => 'Banka Bilgilerimiz (Kurumsal)',
|
||||
'corporate.online-payment' => 'Online Ödeme (Kurumsal)',
|
||||
'corporate.imprint' => 'Künye (Kurumsal)',
|
||||
];
|
||||
|
||||
$fromConfig = collect(config('page_templates.labels', []))
|
||||
->mapWithKeys(fn (string $label, string $key) => [$key => $label])
|
||||
->all();
|
||||
|
||||
return array_merge($base, $fromConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
abstract class StructuredData
|
||||
{
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $graph
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function wrap(array $graph): array
|
||||
{
|
||||
return [
|
||||
'@context' => 'https://schema.org',
|
||||
'@graph' => array_values($graph),
|
||||
];
|
||||
}
|
||||
|
||||
protected static function siteUrl(): string
|
||||
{
|
||||
return url('/');
|
||||
}
|
||||
|
||||
protected static function siteName(): string
|
||||
{
|
||||
return (string) setting('site_name', config('app.name'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function websiteNode(?string $searchUrlTemplate = null): array
|
||||
{
|
||||
$siteUrl = static::siteUrl();
|
||||
$siteName = static::siteName();
|
||||
|
||||
$node = [
|
||||
'@type' => 'WebSite',
|
||||
'@id' => $siteUrl . '#website',
|
||||
'url' => $siteUrl,
|
||||
'name' => $siteName,
|
||||
'inLanguage' => app()->getLocale(),
|
||||
'publisher' => ['@id' => $siteUrl . '#organization'],
|
||||
];
|
||||
|
||||
if ($searchUrlTemplate) {
|
||||
$node['potentialAction'] = [
|
||||
'@type' => 'SearchAction',
|
||||
'target' => [
|
||||
'@type' => 'EntryPoint',
|
||||
'urlTemplate' => $searchUrlTemplate,
|
||||
],
|
||||
'query-input' => 'required name=search_term_string',
|
||||
];
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
protected static function organizationNode(): array
|
||||
{
|
||||
$siteUrl = static::siteUrl();
|
||||
$siteName = static::siteName();
|
||||
|
||||
$node = [
|
||||
'@type' => 'Organization',
|
||||
'@id' => $siteUrl . '#organization',
|
||||
'name' => $siteName,
|
||||
'url' => $siteUrl,
|
||||
];
|
||||
|
||||
$logo = setting('site_logo');
|
||||
if ($logo) {
|
||||
$logoUrl = str_starts_with($logo, 'http') ? $logo : asset('storage/' . ltrim($logo, '/'));
|
||||
$node['logo'] = [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $logoUrl,
|
||||
];
|
||||
}
|
||||
|
||||
$phone = setting('contact_phone');
|
||||
if ($phone) {
|
||||
$node['telephone'] = $phone;
|
||||
}
|
||||
|
||||
$email = setting('contact_email');
|
||||
if ($email) {
|
||||
$node['email'] = $email;
|
||||
}
|
||||
|
||||
$address = setting('contact_address');
|
||||
if ($address) {
|
||||
$node['address'] = [
|
||||
'@type' => 'PostalAddress',
|
||||
'streetAddress' => $address,
|
||||
];
|
||||
}
|
||||
|
||||
$socialLinks = setting('social_links');
|
||||
if ($socialLinks) {
|
||||
$links = is_string($socialLinks) ? json_decode($socialLinks, true) : $socialLinks;
|
||||
if (is_array($links)) {
|
||||
$sameAs = [];
|
||||
foreach ($links as $platform => $url) {
|
||||
if (!empty($url)) {
|
||||
$sameAs[] = $url;
|
||||
}
|
||||
}
|
||||
if (!empty($sameAs)) {
|
||||
$node['sameAs'] = $sameAs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array{name: string, item: string}> $items
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function breadcrumbNode(string $pageUrl, array $items): array
|
||||
{
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $index => $item) {
|
||||
$elements[] = [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $index + 1,
|
||||
'name' => $item['name'],
|
||||
'item' => $item['item'],
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'@type' => 'BreadcrumbList',
|
||||
'@id' => $pageUrl . '#breadcrumb',
|
||||
'itemListElement' => $elements,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $extra
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function webPageNode(
|
||||
string $pageUrl,
|
||||
string $name,
|
||||
?string $description = null,
|
||||
array $extra = [],
|
||||
): array {
|
||||
$node = array_merge([
|
||||
'@type' => 'WebPage',
|
||||
'@id' => $pageUrl . '#webpage',
|
||||
'url' => $pageUrl,
|
||||
'name' => $name,
|
||||
'inLanguage' => app()->getLocale(),
|
||||
'isPartOf' => ['@id' => static::siteUrl() . '#website'],
|
||||
'breadcrumb' => ['@id' => $pageUrl . '#breadcrumb'],
|
||||
], $extra);
|
||||
|
||||
if ($description) {
|
||||
$node['description'] = $description;
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function listItem(int $position, array $item): array
|
||||
{
|
||||
return [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $position,
|
||||
'item' => $item,
|
||||
];
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -65,7 +65,7 @@ return [
|
||||
|
||||
'temporary_file_upload' => [
|
||||
'disk' => 'public', // Example: 'local', 's3' | Default: 'default'
|
||||
'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB)
|
||||
'rules' => ['required', 'file', 'max:51200'], // 50MB limit | Default: ['required', 'file', 'max:12288'] (12MB)
|
||||
'directory' => 'livewire-tmp', // Example: 'tmp' | Default: 'livewire-tmp'
|
||||
'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1'
|
||||
'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs...
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Statik sayfa şablonları için hero görselleri.
|
||||
* Blade şablonları ve Page modeli aynı kaynağı kullanır.
|
||||
*/
|
||||
return [
|
||||
'heroes' => [
|
||||
'services.app-development' => [
|
||||
'image' => 'assets/img/photos/truncgil-mobile-app.png',
|
||||
'webp' => 'assets/img/photos/truncgil-mobile-app.webp',
|
||||
'width' => 735,
|
||||
'height' => 735,
|
||||
],
|
||||
'services.web-development' => [
|
||||
'image' => 'assets/img/illustrations/i12.png',
|
||||
'webp' => 'assets/img/illustrations/i12.webp',
|
||||
'width' => 800,
|
||||
'height' => 600,
|
||||
],
|
||||
'neler-yapariz' => [
|
||||
'image' => 'assets/img/illustrations/3d11.png',
|
||||
'width' => 800,
|
||||
'height' => 1080,
|
||||
],
|
||||
],
|
||||
|
||||
'labels' => [
|
||||
'services.app-development' => 'Uygulama Geliştirme',
|
||||
'services.web-development' => 'Web Uygulamaları',
|
||||
'neler-yapariz' => 'Neler Yaparız',
|
||||
],
|
||||
];
|
||||
@@ -43,4 +43,20 @@ return [
|
||||
'access_key' => env('UNSPLASH_ACCESS_KEY'),
|
||||
],
|
||||
|
||||
'spotify' => [
|
||||
'client_id' => env('SPOTIFY_CLIENT_ID'),
|
||||
'client_secret' => env('SPOTIFY_CLIENT_SECRET'),
|
||||
'artist_id' => env('SPOTIFY_ARTIST_ID'),
|
||||
'market' => env('SPOTIFY_MARKET', 'TR'),
|
||||
],
|
||||
|
||||
'youtube' => [
|
||||
'api_key' => env('YOUTUBE_API_KEY'),
|
||||
'channel_id' => env('YOUTUBE_CHANNEL_ID'),
|
||||
'topic_channel_id' => env('YOUTUBE_TOPIC_CHANNEL_ID'),
|
||||
'playlist_id' => env('YOUTUBE_PLAYLIST_ID'),
|
||||
'api_referer' => env('YOUTUBE_API_REFERER'),
|
||||
'distributor_filter' => env('YOUTUBE_DISTRIBUTOR_FILTER'),
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -0,0 +1,662 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
"site" => array (
|
||||
'Google Play ve App Store\'da' =>
|
||||
array (
|
||||
'en' => 'On Google Play and the App Store, we build',
|
||||
'de' => 'Bei Google Play und im App Store entwickeln wir',
|
||||
'se' => 'On Google Play and the App Store, we build',
|
||||
'ar' => 'On Google Play and the App Store, we build',
|
||||
'ru' => 'В Google Play и App Store мы создаём',
|
||||
),
|
||||
'başarılı uygulamalar' =>
|
||||
array (
|
||||
'en' => 'successful apps',
|
||||
'de' => 'erfolgreiche Apps',
|
||||
'se' => 'successful apps',
|
||||
'ar' => 'successful apps',
|
||||
'ru' => 'успешные приложения',
|
||||
),
|
||||
'geliştiriyoruz.' =>
|
||||
array (
|
||||
'en' => 'for you.',
|
||||
'de' => 'für Sie.',
|
||||
'se' => 'for you.',
|
||||
'ar' => 'for you.',
|
||||
'ru' => 'для вас.',
|
||||
),
|
||||
'Trunçgil Teknoloji olarak Android ve iOS uygulamalarını fikirden mağaza yayınına kadar titizlikle hayata geçiriyoruz.' =>
|
||||
array (
|
||||
'en' => 'At Trunçgil Teknoloji, we meticulously bring Android and iOS apps to life—from idea to store launch.',
|
||||
'de' => 'Bei Trunçgil Teknoloji setzen wir Android- und iOS-Apps sorgfältig um – von der Idee bis zum Store-Launch.',
|
||||
'se' => 'At Trunçgil Teknoloji, we meticulously bring Android and iOS apps to life—from idea to store launch.',
|
||||
'ar' => 'At Trunçgil Teknoloji, we meticulously bring Android and iOS apps to life—from idea to store launch.',
|
||||
'ru' => 'At Trunçgil Teknoloji, we meticulously bring Android and iOS apps to life—from idea to store launch.',
|
||||
),
|
||||
'Projenizi Konuşalım' =>
|
||||
array (
|
||||
'en' => 'Let\'s Talk About Your Project',
|
||||
'de' => 'Let\'s Talk About Your Project',
|
||||
'se' => 'Let\'s Talk About Your Project',
|
||||
'ar' => 'Let\'s Talk About Your Project',
|
||||
'ru' => 'Let\'s Talk About Your Project',
|
||||
),
|
||||
'Trunçgil Teknoloji mobil uygulama geliştirme' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji mobile app development',
|
||||
'de' => 'Trunçgil Teknoloji mobile app development',
|
||||
'se' => 'Trunçgil Teknoloji mobile app development',
|
||||
'ar' => 'Trunçgil Teknoloji mobile app development',
|
||||
'ru' => 'Trunçgil Teknoloji mobile app development',
|
||||
),
|
||||
'Geliştirme Yetkinliklerimiz' =>
|
||||
array (
|
||||
'en' => 'Our Development Capabilities',
|
||||
'de' => 'Unsere Entwicklungskompetenzen',
|
||||
'se' => 'Our Development Capabilities',
|
||||
'ar' => 'Our Development Capabilities',
|
||||
'ru' => 'Наши компетенции в разработке',
|
||||
),
|
||||
'Trunçgil Teknoloji, uygulamanızı mağazaya hazır hale getirmek için' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji considers',
|
||||
'de' => 'Trunçgil Teknoloji denkt an',
|
||||
'se' => 'Trunçgil Teknoloji considers',
|
||||
'ar' => 'Trunçgil Teknoloji considers',
|
||||
'ru' => 'Trunçgil Teknoloji considers',
|
||||
),
|
||||
'her detayı' =>
|
||||
array (
|
||||
'en' => 'every detail',
|
||||
'de' => 'jedes Detail',
|
||||
'se' => 'every detail',
|
||||
'ar' => 'every detail',
|
||||
'ru' => 'every detail',
|
||||
),
|
||||
'düşünür.' =>
|
||||
array (
|
||||
'en' => 'to get your app store-ready.',
|
||||
'de' => 'um Ihre App store-ready zu machen.',
|
||||
'se' => 'to get your app store-ready.',
|
||||
'ar' => 'to get your app store-ready.',
|
||||
'ru' => 'to get your app store-ready.',
|
||||
),
|
||||
'Keşif ve Strateji' =>
|
||||
array (
|
||||
'en' => 'Discovery & Strategy',
|
||||
'de' => 'Discovery & Strategy',
|
||||
'se' => 'Discovery & Strategy',
|
||||
'ar' => 'Discovery & Strategy',
|
||||
'ru' => 'Discovery & Strategy',
|
||||
),
|
||||
'UI/UX Tasarım' =>
|
||||
array (
|
||||
'en' => 'UI/UX Design',
|
||||
'de' => 'UI/UX Design',
|
||||
'se' => 'UI/UX Design',
|
||||
'ar' => 'UI/UX Design',
|
||||
'ru' => 'UI/UX Design',
|
||||
),
|
||||
'Native Android Geliştirme' =>
|
||||
array (
|
||||
'en' => 'Native Android Development',
|
||||
'de' => 'Native Android Development',
|
||||
'se' => 'Native Android Development',
|
||||
'ar' => 'Native Android Development',
|
||||
'ru' => 'Native Android Development',
|
||||
),
|
||||
'iOS Geliştirme' =>
|
||||
array (
|
||||
'en' => 'iOS Development',
|
||||
'de' => 'iOS Development',
|
||||
'se' => 'iOS Development',
|
||||
'ar' => 'iOS Development',
|
||||
'ru' => 'iOS Development',
|
||||
),
|
||||
'Güvenlik ve Test' =>
|
||||
array (
|
||||
'en' => 'Security & Testing',
|
||||
'de' => 'Security & Testing',
|
||||
'se' => 'Security & Testing',
|
||||
'ar' => 'Security & Testing',
|
||||
'ru' => 'Security & Testing',
|
||||
),
|
||||
'Google Play Uyumluluğu' =>
|
||||
array (
|
||||
'en' => 'Google Play Compliance',
|
||||
'de' => 'Google Play Compliance',
|
||||
'se' => 'Google Play Compliance',
|
||||
'ar' => 'Google Play Compliance',
|
||||
'ru' => 'Google Play Compliance',
|
||||
),
|
||||
'App Store İnceleme Hazırlığı' =>
|
||||
array (
|
||||
'en' => 'App Store Review Readiness',
|
||||
'de' => 'App Store Review Readiness',
|
||||
'se' => 'App Store Review Readiness',
|
||||
'ar' => 'App Store Review Readiness',
|
||||
'ru' => 'App Store Review Readiness',
|
||||
),
|
||||
'Yayın Sonrası Destek' =>
|
||||
array (
|
||||
'en' => 'Post-Launch Support',
|
||||
'de' => 'Post-Launch Support',
|
||||
'se' => 'Post-Launch Support',
|
||||
'ar' => 'Post-Launch Support',
|
||||
'ru' => 'Post-Launch Support',
|
||||
),
|
||||
'Geliştirme Sürecimiz' =>
|
||||
array (
|
||||
'en' => 'Our Development Process',
|
||||
'de' => 'Our Development Process',
|
||||
'se' => 'Our Development Process',
|
||||
'ar' => 'Our Development Process',
|
||||
'ru' => 'Our Development Process',
|
||||
),
|
||||
'Trunçgil Teknoloji ile fikirden mağazaya' =>
|
||||
array (
|
||||
'en' => 'With Trunçgil Teknoloji, from idea to store in',
|
||||
'de' => 'With Trunçgil Teknoloji, from idea to store in',
|
||||
'se' => 'With Trunçgil Teknoloji, from idea to store in',
|
||||
'ar' => 'With Trunçgil Teknoloji, from idea to store in',
|
||||
'ru' => 'With Trunçgil Teknoloji, from idea to store in',
|
||||
),
|
||||
'dört adımda' =>
|
||||
array (
|
||||
'en' => 'four steps',
|
||||
'de' => 'four steps',
|
||||
'se' => 'four steps',
|
||||
'ar' => 'four steps',
|
||||
'ru' => 'four steps',
|
||||
),
|
||||
'ilerleyin.' =>
|
||||
array (
|
||||
'en' => '— here\'s how.',
|
||||
'de' => '— here\'s how.',
|
||||
'se' => '— here\'s how.',
|
||||
'ar' => '— here\'s how.',
|
||||
'ru' => '— here\'s how.',
|
||||
),
|
||||
'Trunçgil Teknoloji uygulama geliştirme süreci' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji app development process',
|
||||
'de' => 'Trunçgil Teknoloji app development process',
|
||||
'se' => 'Trunçgil Teknoloji app development process',
|
||||
'ar' => 'Trunçgil Teknoloji app development process',
|
||||
'ru' => 'Trunçgil Teknoloji app development process',
|
||||
),
|
||||
'Keşif ve Analiz' =>
|
||||
array (
|
||||
'en' => 'Discovery & Analysis',
|
||||
'de' => 'Discovery & Analysis',
|
||||
'se' => 'Discovery & Analysis',
|
||||
'ar' => 'Discovery & Analysis',
|
||||
'ru' => 'Discovery & Analysis',
|
||||
),
|
||||
'Trunçgil Teknoloji ekibi, hedef kitlenizi, iş hedeflerinizi ve teknik gereksinimlerinizi derinlemesine analiz ederek projenin yol haritasını birlikte oluşturur.' =>
|
||||
array (
|
||||
'en' => 'The Trunçgil Teknoloji team deeply analyzes your audience, business goals, and technical requirements to co-create the project roadmap.',
|
||||
'de' => 'The Trunçgil Teknoloji team deeply analyzes your audience, business goals, and technical requirements to co-create the project roadmap.',
|
||||
'se' => 'The Trunçgil Teknoloji team deeply analyzes your audience, business goals, and technical requirements to co-create the project roadmap.',
|
||||
'ar' => 'The Trunçgil Teknoloji team deeply analyzes your audience, business goals, and technical requirements to co-create the project roadmap.',
|
||||
'ru' => 'The Trunçgil Teknoloji team deeply analyzes your audience, business goals, and technical requirements to co-create the project roadmap.',
|
||||
),
|
||||
'Tasarım ve Prototip' =>
|
||||
array (
|
||||
'en' => 'Design & Prototype',
|
||||
'de' => 'Design & Prototype',
|
||||
'se' => 'Design & Prototype',
|
||||
'ar' => 'Design & Prototype',
|
||||
'ru' => 'Design & Prototype',
|
||||
),
|
||||
'Kullanıcı deneyimi öncelikli arayüz tasarımları ve tıklanabilir prototiplerle Google Play ve App Store standartlarına uygun bir deneyim planlanır.' =>
|
||||
array (
|
||||
'en' => 'UX-first interface designs and clickable prototypes are planned to meet Google Play and App Store standards.',
|
||||
'de' => 'UX-first interface designs and clickable prototypes are planned to meet Google Play and App Store standards.',
|
||||
'se' => 'UX-first interface designs and clickable prototypes are planned to meet Google Play and App Store standards.',
|
||||
'ar' => 'UX-first interface designs and clickable prototypes are planned to meet Google Play and App Store standards.',
|
||||
'ru' => 'UX-first interface designs and clickable prototypes are planned to meet Google Play and App Store standards.',
|
||||
),
|
||||
'Geliştirme ve Test' =>
|
||||
array (
|
||||
'en' => 'Development & Testing',
|
||||
'de' => 'Development & Testing',
|
||||
'se' => 'Development & Testing',
|
||||
'ar' => 'Development & Testing',
|
||||
'ru' => 'Development & Testing',
|
||||
),
|
||||
'Android ve iOS platformlarında titiz kodlama, otomatik testler ve cihaz uyumluluğu kontrolleriyle uygulamanız hatasız şekilde inşa edilir.' =>
|
||||
array (
|
||||
'en' => 'Your app is built flawlessly with meticulous coding, automated tests, and device compatibility checks on Android and iOS.',
|
||||
'de' => 'Your app is built flawlessly with meticulous coding, automated tests, and device compatibility checks on Android and iOS.',
|
||||
'se' => 'Your app is built flawlessly with meticulous coding, automated tests, and device compatibility checks on Android and iOS.',
|
||||
'ar' => 'Your app is built flawlessly with meticulous coding, automated tests, and device compatibility checks on Android and iOS.',
|
||||
'ru' => 'Your app is built flawlessly with meticulous coding, automated tests, and device compatibility checks on Android and iOS.',
|
||||
),
|
||||
'Mağaza Yayını ve Destek' =>
|
||||
array (
|
||||
'en' => 'Store Launch & Support',
|
||||
'de' => 'Store Launch & Support',
|
||||
'se' => 'Store Launch & Support',
|
||||
'ar' => 'Store Launch & Support',
|
||||
'ru' => 'Store Launch & Support',
|
||||
),
|
||||
'Google Play Console ve App Store Connect süreçlerini Trunçgil Teknoloji yönetir; yayın sonrası izleme, güncelleme ve bakım desteği sunar.' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji manages Google Play Console and App Store Connect processes, plus post-launch monitoring, updates, and maintenance.',
|
||||
'de' => 'Trunçgil Teknoloji manages Google Play Console and App Store Connect processes, plus post-launch monitoring, updates, and maintenance.',
|
||||
'se' => 'Trunçgil Teknoloji manages Google Play Console and App Store Connect processes, plus post-launch monitoring, updates, and maintenance.',
|
||||
'ar' => 'Trunçgil Teknoloji manages Google Play Console and App Store Connect processes, plus post-launch monitoring, updates, and maintenance.',
|
||||
'ru' => 'Trunçgil Teknoloji manages Google Play Console and App Store Connect processes, plus post-launch monitoring, updates, and maintenance.',
|
||||
),
|
||||
'Sık Sorulan Sorular' =>
|
||||
array (
|
||||
'en' => 'Frequently Asked Questions',
|
||||
'de' => 'Häufig gestellte Fragen',
|
||||
'se' => 'Frequently Asked Questions',
|
||||
'ar' => 'Frequently Asked Questions',
|
||||
'ru' => 'Часто задаваемые вопросы',
|
||||
),
|
||||
'Uygulama geliştirme hakkında' =>
|
||||
array (
|
||||
'en' => 'About app development —',
|
||||
'de' => 'About app development —',
|
||||
'se' => 'About app development —',
|
||||
'ar' => 'About app development —',
|
||||
'ru' => 'About app development —',
|
||||
),
|
||||
'merak ettikleriniz' =>
|
||||
array (
|
||||
'en' => 'your questions answered',
|
||||
'de' => 'your questions answered',
|
||||
'se' => 'your questions answered',
|
||||
'ar' => 'your questions answered',
|
||||
'ru' => 'your questions answered',
|
||||
),
|
||||
'Trunçgil Teknoloji hangi platformlarda uygulama geliştiriyor?' =>
|
||||
array (
|
||||
'en' => 'Which platforms does Trunçgil Teknoloji develop apps for?',
|
||||
'de' => 'Which platforms does Trunçgil Teknoloji develop apps for?',
|
||||
'se' => 'Which platforms does Trunçgil Teknoloji develop apps for?',
|
||||
'ar' => 'Which platforms does Trunçgil Teknoloji develop apps for?',
|
||||
'ru' => 'Which platforms does Trunçgil Teknoloji develop apps for?',
|
||||
),
|
||||
'Trunçgil Teknoloji olarak native Android (Kotlin), native iOS (Swift) ve cross-platform (Flutter) çözümler sunuyoruz. Uygulamalarınız Google Play Store ve Apple App Store\'da yayınlanmaya hazır şekilde teslim edilir.' =>
|
||||
array (
|
||||
'en' => 'At Trunçgil Teknoloji, we offer native Android (Kotlin), native iOS (Swift), and cross-platform (Flutter) solutions. Your apps are delivered ready for Google Play Store and Apple App Store.',
|
||||
'de' => 'At Trunçgil Teknoloji, we offer native Android (Kotlin), native iOS (Swift), and cross-platform (Flutter) solutions. Your apps are delivered ready for Google Play Store and Apple App Store.',
|
||||
'se' => 'At Trunçgil Teknoloji, we offer native Android (Kotlin), native iOS (Swift), and cross-platform (Flutter) solutions. Your apps are delivered ready for Google Play Store and Apple App Store.',
|
||||
'ar' => 'At Trunçgil Teknoloji, we offer native Android (Kotlin), native iOS (Swift), and cross-platform (Flutter) solutions. Your apps are delivered ready for Google Play Store and Apple App Store.',
|
||||
'ru' => 'At Trunçgil Teknoloji, we offer native Android (Kotlin), native iOS (Swift), and cross-platform (Flutter) solutions. Your apps are delivered ready for Google Play Store and Apple App Store.',
|
||||
),
|
||||
'Google Play\'e yükleme sürecini siz mi yönetiyorsunuz?' =>
|
||||
array (
|
||||
'en' => 'Do you manage the Google Play upload process?',
|
||||
'de' => 'Do you manage the Google Play upload process?',
|
||||
'se' => 'Do you manage the Google Play upload process?',
|
||||
'ar' => 'Do you manage the Google Play upload process?',
|
||||
'ru' => 'Do you manage the Google Play upload process?',
|
||||
),
|
||||
'Evet. Trunçgil Teknoloji, Google Play Console hesap kurulumu, uygulama imzalama, store listing hazırlığı, ekran görüntüleri ve politika uyumluluk kontrollerini sizin adınıza titizlikle yürütür.' =>
|
||||
array (
|
||||
'en' => 'Yes. Trunçgil Teknoloji meticulously handles Google Play Console setup, app signing, store listing, screenshots, and policy compliance on your behalf.',
|
||||
'de' => 'Yes. Trunçgil Teknoloji meticulously handles Google Play Console setup, app signing, store listing, screenshots, and policy compliance on your behalf.',
|
||||
'se' => 'Yes. Trunçgil Teknoloji meticulously handles Google Play Console setup, app signing, store listing, screenshots, and policy compliance on your behalf.',
|
||||
'ar' => 'Yes. Trunçgil Teknoloji meticulously handles Google Play Console setup, app signing, store listing, screenshots, and policy compliance on your behalf.',
|
||||
'ru' => 'Yes. Trunçgil Teknoloji meticulously handles Google Play Console setup, app signing, store listing, screenshots, and policy compliance on your behalf.',
|
||||
),
|
||||
'App Store inceleme sürecinde red almamak için ne yapıyorsunuz?' =>
|
||||
array (
|
||||
'en' => 'How do you avoid App Store rejections?',
|
||||
'de' => 'How do you avoid App Store rejections?',
|
||||
'se' => 'How do you avoid App Store rejections?',
|
||||
'ar' => 'How do you avoid App Store rejections?',
|
||||
'ru' => 'How do you avoid App Store rejections?',
|
||||
),
|
||||
'Apple\'ın Human Interface Guidelines, gizlilik politikası ve uygulama içi satın alma kurallarına uygunluk denetimi yapıyoruz. Metadata, açıklamalar ve gizlilik etiketleri yayın öncesinde Trunçgil Teknoloji tarafından kontrol edilir.' =>
|
||||
array (
|
||||
'en' => 'We audit compliance with Apple\'s Human Interface Guidelines, privacy policy, and in-app purchase rules. Metadata, descriptions, and privacy labels are checked by Trunçgil Teknoloji before submission.',
|
||||
'de' => 'We audit compliance with Apple\'s Human Interface Guidelines, privacy policy, and in-app purchase rules. Metadata, descriptions, and privacy labels are checked by Trunçgil Teknoloji before submission.',
|
||||
'se' => 'We audit compliance with Apple\'s Human Interface Guidelines, privacy policy, and in-app purchase rules. Metadata, descriptions, and privacy labels are checked by Trunçgil Teknoloji before submission.',
|
||||
'ar' => 'We audit compliance with Apple\'s Human Interface Guidelines, privacy policy, and in-app purchase rules. Metadata, descriptions, and privacy labels are checked by Trunçgil Teknoloji before submission.',
|
||||
'ru' => 'We audit compliance with Apple\'s Human Interface Guidelines, privacy policy, and in-app purchase rules. Metadata, descriptions, and privacy labels are checked by Trunçgil Teknoloji before submission.',
|
||||
),
|
||||
'Bir uygulama projesi ne kadar sürer?' =>
|
||||
array (
|
||||
'en' => 'How long does an app project take?',
|
||||
'de' => 'How long does an app project take?',
|
||||
'se' => 'How long does an app project take?',
|
||||
'ar' => 'How long does an app project take?',
|
||||
'ru' => 'How long does an app project take?',
|
||||
),
|
||||
'Proje kapsamına göre değişmekle birlikte, orta ölçekli bir uygulama Trunçgil Teknoloji sürecinde genellikle 8–16 hafta arasında tamamlanır. Keşif aşamasında size net bir zaman çizelgesi sunulur.' =>
|
||||
array (
|
||||
'en' => 'Depending on scope, a mid-size app typically takes 8–16 weeks in the Trunçgil Teknoloji process. A clear timeline is provided during discovery.',
|
||||
'de' => 'Depending on scope, a mid-size app typically takes 8–16 weeks in the Trunçgil Teknoloji process. A clear timeline is provided during discovery.',
|
||||
'se' => 'Depending on scope, a mid-size app typically takes 8–16 weeks in the Trunçgil Teknoloji process. A clear timeline is provided during discovery.',
|
||||
'ar' => 'Depending on scope, a mid-size app typically takes 8–16 weeks in the Trunçgil Teknoloji process. A clear timeline is provided during discovery.',
|
||||
'ru' => 'Depending on scope, a mid-size app typically takes 8–16 weeks in the Trunçgil Teknoloji process. A clear timeline is provided during discovery.',
|
||||
),
|
||||
'Mevcut uygulamamı yeniden yazabilir veya güncelleyebilir misiniz?' =>
|
||||
array (
|
||||
'en' => 'Can you rewrite or update my existing app?',
|
||||
'de' => 'Can you rewrite or update my existing app?',
|
||||
'se' => 'Can you rewrite or update my existing app?',
|
||||
'ar' => 'Can you rewrite or update my existing app?',
|
||||
'ru' => 'Can you rewrite or update my existing app?',
|
||||
),
|
||||
'Kesinlikle. Trunçgil Teknoloji, legacy kod modernizasyonu, performans iyileştirmesi, yeni OS sürümlerine uyum ve mağaza güncellemeleri konularında kapsamlı destek sağlar.' =>
|
||||
array (
|
||||
'en' => 'Absolutely. Trunçgil Teknoloji provides comprehensive support for legacy modernization, performance improvements, new OS compatibility, and store updates.',
|
||||
'de' => 'Absolutely. Trunçgil Teknoloji provides comprehensive support for legacy modernization, performance improvements, new OS compatibility, and store updates.',
|
||||
'se' => 'Absolutely. Trunçgil Teknoloji provides comprehensive support for legacy modernization, performance improvements, new OS compatibility, and store updates.',
|
||||
'ar' => 'Absolutely. Trunçgil Teknoloji provides comprehensive support for legacy modernization, performance improvements, new OS compatibility, and store updates.',
|
||||
'ru' => 'Absolutely. Trunçgil Teknoloji provides comprehensive support for legacy modernization, performance improvements, new OS compatibility, and store updates.',
|
||||
),
|
||||
'Yayın sonrası bakım hizmeti veriyor musunuz?' =>
|
||||
array (
|
||||
'en' => 'Do you offer post-launch maintenance?',
|
||||
'de' => 'Do you offer post-launch maintenance?',
|
||||
'se' => 'Do you offer post-launch maintenance?',
|
||||
'ar' => 'Do you offer post-launch maintenance?',
|
||||
'ru' => 'Do you offer post-launch maintenance?',
|
||||
),
|
||||
'Evet. Trunçgil Teknoloji, yayın sonrası hata takibi, kullanıcı geri bildirimlerine yanıt, güvenlik yamaları ve periyodik özellik güncellemeleri için esnek bakım paketleri sunar.' =>
|
||||
array (
|
||||
'en' => 'Yes. Trunçgil Teknoloji offers flexible maintenance packages for post-launch bug tracking, user feedback, security patches, and periodic feature updates.',
|
||||
'de' => 'Yes. Trunçgil Teknoloji offers flexible maintenance packages for post-launch bug tracking, user feedback, security patches, and periodic feature updates.',
|
||||
'se' => 'Yes. Trunçgil Teknoloji offers flexible maintenance packages for post-launch bug tracking, user feedback, security patches, and periodic feature updates.',
|
||||
'ar' => 'Yes. Trunçgil Teknoloji offers flexible maintenance packages for post-launch bug tracking, user feedback, security patches, and periodic feature updates.',
|
||||
'ru' => 'Yes. Trunçgil Teknoloji offers flexible maintenance packages for post-launch bug tracking, user feedback, security patches, and periodic feature updates.',
|
||||
),
|
||||
'Neden Trunçgil Teknoloji?' =>
|
||||
array (
|
||||
'en' => 'Why Trunçgil Teknoloji?',
|
||||
'de' => 'Warum Trunçgil Teknoloji?',
|
||||
'se' => 'Why Trunçgil Teknoloji?',
|
||||
'ar' => 'Why Trunçgil Teknoloji?',
|
||||
'ru' => 'Почему Trunçgil Teknoloji?',
|
||||
),
|
||||
'Google Play ve App Store\'da başarılı olmanız için' =>
|
||||
array (
|
||||
'en' => 'Six strong reasons to succeed on',
|
||||
'de' => 'Six strong reasons to succeed on',
|
||||
'se' => 'Six strong reasons to succeed on',
|
||||
'ar' => 'Six strong reasons to succeed on',
|
||||
'ru' => 'Six strong reasons to succeed on',
|
||||
),
|
||||
'6 güçlü neden' =>
|
||||
array (
|
||||
'en' => 'Google Play & App Store',
|
||||
'de' => 'Google Play & App Store',
|
||||
'se' => 'Google Play & App Store',
|
||||
'ar' => 'Google Play & App Store',
|
||||
'ru' => 'Google Play & App Store',
|
||||
),
|
||||
'Trunçgil Teknoloji mobil uygulamalar' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji mobile apps',
|
||||
'de' => 'Trunçgil Teknoloji mobile apps',
|
||||
'se' => 'Trunçgil Teknoloji mobile apps',
|
||||
'ar' => 'Trunçgil Teknoloji mobile apps',
|
||||
'ru' => 'Trunçgil Teknoloji mobile apps',
|
||||
),
|
||||
'Android ve iOS Uzmanlığı' =>
|
||||
array (
|
||||
'en' => 'Android & iOS Expertise',
|
||||
'de' => 'Android & iOS Expertise',
|
||||
'se' => 'Android & iOS Expertise',
|
||||
'ar' => 'Android & iOS Expertise',
|
||||
'ru' => 'Android & iOS Expertise',
|
||||
),
|
||||
'Trunçgil Teknoloji, Kotlin, Swift ve Flutter teknolojilerinde deneyimli ekibiyle her iki platformda da performanslı uygulamalar geliştirir.' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji builds high-performance apps on both platforms with an experienced team in Kotlin, Swift, and Flutter.',
|
||||
'de' => 'Trunçgil Teknoloji builds high-performance apps on both platforms with an experienced team in Kotlin, Swift, and Flutter.',
|
||||
'se' => 'Trunçgil Teknoloji builds high-performance apps on both platforms with an experienced team in Kotlin, Swift, and Flutter.',
|
||||
'ar' => 'Trunçgil Teknoloji builds high-performance apps on both platforms with an experienced team in Kotlin, Swift, and Flutter.',
|
||||
'ru' => 'Trunçgil Teknoloji builds high-performance apps on both platforms with an experienced team in Kotlin, Swift, and Flutter.',
|
||||
),
|
||||
'Google Play Politika Uyumu' =>
|
||||
array (
|
||||
'en' => 'Google Play Policy Compliance',
|
||||
'de' => 'Google Play Policy Compliance',
|
||||
'se' => 'Google Play Policy Compliance',
|
||||
'ar' => 'Google Play Policy Compliance',
|
||||
'ru' => 'Google Play Policy Compliance',
|
||||
),
|
||||
'Veri güvenliği, izin yönetimi ve içerik politikalarına tam uyum sağlayarak uygulamanızın mağazada sorunsuz yayınlanmasını garanti altına alırız.' =>
|
||||
array (
|
||||
'en' => 'We ensure full compliance with data security, permissions, and content policies for smooth store publication.',
|
||||
'de' => 'We ensure full compliance with data security, permissions, and content policies for smooth store publication.',
|
||||
'se' => 'We ensure full compliance with data security, permissions, and content policies for smooth store publication.',
|
||||
'ar' => 'We ensure full compliance with data security, permissions, and content policies for smooth store publication.',
|
||||
'ru' => 'We ensure full compliance with data security, permissions, and content policies for smooth store publication.',
|
||||
),
|
||||
'App Store İnceleme Deneyimi' =>
|
||||
array (
|
||||
'en' => 'App Store Review Experience',
|
||||
'de' => 'App Store Review Experience',
|
||||
'se' => 'App Store Review Experience',
|
||||
'ar' => 'App Store Review Experience',
|
||||
'ru' => 'App Store Review Experience',
|
||||
),
|
||||
'Human Interface Guidelines, gizlilik etiketleri ve metadata gereksinimlerini önceden ele alarak red riskini minimuma indiriyoruz.' =>
|
||||
array (
|
||||
'en' => 'We minimize rejection risk by addressing Human Interface Guidelines, privacy labels, and metadata requirements upfront.',
|
||||
'de' => 'We minimize rejection risk by addressing Human Interface Guidelines, privacy labels, and metadata requirements upfront.',
|
||||
'se' => 'We minimize rejection risk by addressing Human Interface Guidelines, privacy labels, and metadata requirements upfront.',
|
||||
'ar' => 'We minimize rejection risk by addressing Human Interface Guidelines, privacy labels, and metadata requirements upfront.',
|
||||
'ru' => 'We minimize rejection risk by addressing Human Interface Guidelines, privacy labels, and metadata requirements upfront.',
|
||||
),
|
||||
'Kapsamlı Test Süreci' =>
|
||||
array (
|
||||
'en' => 'Comprehensive Testing Process',
|
||||
'de' => 'Comprehensive Testing Process',
|
||||
'se' => 'Comprehensive Testing Process',
|
||||
'ar' => 'Comprehensive Testing Process',
|
||||
'ru' => 'Comprehensive Testing Process',
|
||||
),
|
||||
'Birim testleri, entegrasyon testleri ve gerçek cihazlarda manuel QA ile uygulamanızı yayına hazır hale getiriyoruz.' =>
|
||||
array (
|
||||
'en' => 'Unit tests, integration tests, and manual QA on real devices prepare your app for launch.',
|
||||
'de' => 'Unit tests, integration tests, and manual QA on real devices prepare your app for launch.',
|
||||
'se' => 'Unit tests, integration tests, and manual QA on real devices prepare your app for launch.',
|
||||
'ar' => 'Unit tests, integration tests, and manual QA on real devices prepare your app for launch.',
|
||||
'ru' => 'Unit tests, integration tests, and manual QA on real devices prepare your app for launch.',
|
||||
),
|
||||
'Performans Optimizasyonu' =>
|
||||
array (
|
||||
'en' => 'Performance Optimization',
|
||||
'de' => 'Performance Optimization',
|
||||
'se' => 'Performance Optimization',
|
||||
'ar' => 'Performance Optimization',
|
||||
'ru' => 'Performance Optimization',
|
||||
),
|
||||
'Pil tüketimi, açılış süresi ve bellek kullanımı Trunçgil Teknoloji tarafından sürekli ölçülür ve iyileştirilir.' =>
|
||||
array (
|
||||
'en' => 'Battery usage, launch time, and memory consumption are continuously measured and improved by Trunçgil Teknoloji.',
|
||||
'de' => 'Battery usage, launch time, and memory consumption are continuously measured and improved by Trunçgil Teknoloji.',
|
||||
'se' => 'Battery usage, launch time, and memory consumption are continuously measured and improved by Trunçgil Teknoloji.',
|
||||
'ar' => 'Battery usage, launch time, and memory consumption are continuously measured and improved by Trunçgil Teknoloji.',
|
||||
'ru' => 'Battery usage, launch time, and memory consumption are continuously measured and improved by Trunçgil Teknoloji.',
|
||||
),
|
||||
'Sürekli Bakım ve Destek' =>
|
||||
array (
|
||||
'en' => 'Ongoing Maintenance & Support',
|
||||
'de' => 'Ongoing Maintenance & Support',
|
||||
'se' => 'Ongoing Maintenance & Support',
|
||||
'ar' => 'Ongoing Maintenance & Support',
|
||||
'ru' => 'Ongoing Maintenance & Support',
|
||||
),
|
||||
'Yayın sonrası hata düzeltmeleri, OS güncellemelerine uyum ve yeni özellik geliştirmeleri için yanınızdayız.' =>
|
||||
array (
|
||||
'en' => 'We\'re with you for post-launch bug fixes, OS update compatibility, and new feature development.',
|
||||
'de' => 'We\'re with you for post-launch bug fixes, OS update compatibility, and new feature development.',
|
||||
'se' => 'We\'re with you for post-launch bug fixes, OS update compatibility, and new feature development.',
|
||||
'ar' => 'We\'re with you for post-launch bug fixes, OS update compatibility, and new feature development.',
|
||||
'ru' => 'We\'re with you for post-launch bug fixes, OS update compatibility, and new feature development.',
|
||||
),
|
||||
'Mutlu Müşteriler' =>
|
||||
array (
|
||||
'en' => 'Happy Clients',
|
||||
'de' => 'Zufriedene Kunden',
|
||||
'se' => 'Happy Clients',
|
||||
'ar' => 'Happy Clients',
|
||||
'ru' => 'Довольные клиенты',
|
||||
),
|
||||
'Trunçgil Teknoloji ile çalışan müşterilerimizin' =>
|
||||
array (
|
||||
'en' => 'What our clients say about working with',
|
||||
'de' => 'What our clients say about working with',
|
||||
'se' => 'What our clients say about working with',
|
||||
'ar' => 'What our clients say about working with',
|
||||
'ru' => 'What our clients say about working with',
|
||||
),
|
||||
'deneyimleri' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji',
|
||||
'de' => 'Trunçgil Teknoloji',
|
||||
'se' => 'Trunçgil Teknoloji',
|
||||
'ar' => 'Trunçgil Teknoloji',
|
||||
'ru' => 'Trunçgil Teknoloji',
|
||||
),
|
||||
'Trunçgil Teknoloji, e-ticaret uygulamamızı Google Play\'e sorunsuz taşıdı. İnceleme sürecinde hiç red almadık; her adımda bizi bilgilendirdiler.' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji smoothly brought our e-commerce app to Google Play. We got zero rejections—they kept us informed at every step.',
|
||||
'de' => 'Trunçgil Teknoloji smoothly brought our e-commerce app to Google Play. We got zero rejections—they kept us informed at every step.',
|
||||
'se' => 'Trunçgil Teknoloji smoothly brought our e-commerce app to Google Play. We got zero rejections—they kept us informed at every step.',
|
||||
'ar' => 'Trunçgil Teknoloji smoothly brought our e-commerce app to Google Play. We got zero rejections—they kept us informed at every step.',
|
||||
'ru' => 'Trunçgil Teknoloji smoothly brought our e-commerce app to Google Play. We got zero rejections—they kept us informed at every step.',
|
||||
),
|
||||
'Kurucu, Perakende Startup' =>
|
||||
array (
|
||||
'en' => 'Founder, Retail Startup',
|
||||
'de' => 'Founder, Retail Startup',
|
||||
'se' => 'Founder, Retail Startup',
|
||||
'ar' => 'Founder, Retail Startup',
|
||||
'ru' => 'Founder, Retail Startup',
|
||||
),
|
||||
'iOS uygulamamız App Store\'da ilk denemede onaylandı. Trunçgil Teknoloji\'nin titiz test süreci ve tasarım kalitesi gerçekten fark yarattı.' =>
|
||||
array (
|
||||
'en' => 'Our iOS app was approved on the first App Store submission. Trunçgil Teknoloji\'s rigorous testing and design quality made the difference.',
|
||||
'de' => 'Our iOS app was approved on the first App Store submission. Trunçgil Teknoloji\'s rigorous testing and design quality made the difference.',
|
||||
'se' => 'Our iOS app was approved on the first App Store submission. Trunçgil Teknoloji\'s rigorous testing and design quality made the difference.',
|
||||
'ar' => 'Our iOS app was approved on the first App Store submission. Trunçgil Teknoloji\'s rigorous testing and design quality made the difference.',
|
||||
'ru' => 'Our iOS app was approved on the first App Store submission. Trunçgil Teknoloji\'s rigorous testing and design quality made the difference.',
|
||||
),
|
||||
'Ürün Müdürü, SaaS Şirketi' =>
|
||||
array (
|
||||
'en' => 'Product Manager, SaaS Company',
|
||||
'de' => 'Product Manager, SaaS Company',
|
||||
'se' => 'Product Manager, SaaS Company',
|
||||
'ar' => 'Product Manager, SaaS Company',
|
||||
'ru' => 'Product Manager, SaaS Company',
|
||||
),
|
||||
'Hem Android hem iOS sürümünü aynı anda teslim ettiler. Trunçgil Teknoloji ekibi proje boyunca şeffaf iletişim kurdu ve söz verilen tarihte yayınladık.' =>
|
||||
array (
|
||||
'en' => 'They delivered both Android and iOS versions simultaneously. The Trunçgil Teknoloji team communicated transparently and we launched on schedule.',
|
||||
'de' => 'They delivered both Android and iOS versions simultaneously. The Trunçgil Teknoloji team communicated transparently and we launched on schedule.',
|
||||
'se' => 'They delivered both Android and iOS versions simultaneously. The Trunçgil Teknoloji team communicated transparently and we launched on schedule.',
|
||||
'ar' => 'They delivered both Android and iOS versions simultaneously. The Trunçgil Teknoloji team communicated transparently and we launched on schedule.',
|
||||
'ru' => 'They delivered both Android and iOS versions simultaneously. The Trunçgil Teknoloji team communicated transparently and we launched on schedule.',
|
||||
),
|
||||
'CTO, Fintech Girişimi' =>
|
||||
array (
|
||||
'en' => 'CTO, Fintech Startup',
|
||||
'de' => 'CTO, Fintech Startup',
|
||||
'se' => 'CTO, Fintech Startup',
|
||||
'ar' => 'CTO, Fintech Startup',
|
||||
'ru' => 'CTO, Fintech Startup',
|
||||
),
|
||||
'Yayın sonrası destek paketleri sayesinde uygulamamız her yeni Android sürümüne sorunsuz uyum sağlıyor. Trunçgil Teknoloji\'ye güveniyoruz.' =>
|
||||
array (
|
||||
'en' => 'Thanks to post-launch support, our app adapts smoothly to every new Android version. We trust Trunçgil Teknoloji.',
|
||||
'de' => 'Thanks to post-launch support, our app adapts smoothly to every new Android version. We trust Trunçgil Teknoloji.',
|
||||
'se' => 'Thanks to post-launch support, our app adapts smoothly to every new Android version. We trust Trunçgil Teknoloji.',
|
||||
'ar' => 'Thanks to post-launch support, our app adapts smoothly to every new Android version. We trust Trunçgil Teknoloji.',
|
||||
'ru' => 'Thanks to post-launch support, our app adapts smoothly to every new Android version. We trust Trunçgil Teknoloji.',
|
||||
),
|
||||
'Operasyon Direktörü' =>
|
||||
array (
|
||||
'en' => 'Operations Director',
|
||||
'de' => 'Operations Director',
|
||||
'se' => 'Operations Director',
|
||||
'ar' => 'Operations Director',
|
||||
'ru' => 'Operations Director',
|
||||
),
|
||||
'Kurumsal mobil uygulamamızın güvenlik gereksinimlerini Trunçgil Teknoloji mükemmel karşıladı. Google Play ve kurumsal MDM dağıtımı sorunsuz tamamlandı.' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji perfectly met our enterprise app security requirements. Google Play and MDM deployment completed flawlessly.',
|
||||
'de' => 'Trunçgil Teknoloji perfectly met our enterprise app security requirements. Google Play and MDM deployment completed flawlessly.',
|
||||
'se' => 'Trunçgil Teknoloji perfectly met our enterprise app security requirements. Google Play and MDM deployment completed flawlessly.',
|
||||
'ar' => 'Trunçgil Teknoloji perfectly met our enterprise app security requirements. Google Play and MDM deployment completed flawlessly.',
|
||||
'ru' => 'Trunçgil Teknoloji perfectly met our enterprise app security requirements. Google Play and MDM deployment completed flawlessly.',
|
||||
),
|
||||
'IT Müdürü, Holding' =>
|
||||
array (
|
||||
'en' => 'IT Manager, Holding Company',
|
||||
'de' => 'IT Manager, Holding Company',
|
||||
'se' => 'IT Manager, Holding Company',
|
||||
'ar' => 'IT Manager, Holding Company',
|
||||
'ru' => 'IT Manager, Holding Company',
|
||||
),
|
||||
'Prototipten mağaza yayınına kadar tüm süreçte Trunçgil Teknoloji yanımızdaydı. Kullanıcılarımız uygulamanın akıcılığından çok memnun.' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji was with us from prototype to store launch. Our users love how smooth the app feels.',
|
||||
'de' => 'Trunçgil Teknoloji was with us from prototype to store launch. Our users love how smooth the app feels.',
|
||||
'se' => 'Trunçgil Teknoloji was with us from prototype to store launch. Our users love how smooth the app feels.',
|
||||
'ar' => 'Trunçgil Teknoloji was with us from prototype to store launch. Our users love how smooth the app feels.',
|
||||
'ru' => 'Trunçgil Teknoloji was with us from prototype to store launch. Our users love how smooth the app feels.',
|
||||
),
|
||||
'Pazarlama Müdürü' =>
|
||||
array (
|
||||
'en' => 'Marketing Manager',
|
||||
'de' => 'Marketing Manager',
|
||||
'se' => 'Marketing Manager',
|
||||
'ar' => 'Marketing Manager',
|
||||
'ru' => 'Marketing Manager',
|
||||
),
|
||||
'Uygulamanızı Google Play ve App Store\'da' =>
|
||||
array (
|
||||
'en' => 'Let\'s launch your app on',
|
||||
'de' => 'Let\'s launch your app on',
|
||||
'se' => 'Let\'s launch your app on',
|
||||
'ar' => 'Let\'s launch your app on',
|
||||
'ru' => 'Let\'s launch your app on',
|
||||
),
|
||||
'hayata geçirelim.' =>
|
||||
array (
|
||||
'en' => 'Google Play & App Store.',
|
||||
'de' => 'Google Play & App Store.',
|
||||
'se' => 'Google Play & App Store.',
|
||||
'ar' => 'Google Play & App Store.',
|
||||
'ru' => 'Google Play & App Store.',
|
||||
),
|
||||
'Trunçgil Teknoloji ekibi, projenizi dinlemeye ve size özel bir yol haritası sunmaya hazır.' =>
|
||||
array (
|
||||
'en' => 'The Trunçgil Teknoloji team is ready to hear your project and offer a tailored roadmap.',
|
||||
'de' => 'The Trunçgil Teknoloji team is ready to hear your project and offer a tailored roadmap.',
|
||||
'se' => 'The Trunçgil Teknoloji team is ready to hear your project and offer a tailored roadmap.',
|
||||
'ar' => 'The Trunçgil Teknoloji team is ready to hear your project and offer a tailored roadmap.',
|
||||
'ru' => 'The Trunçgil Teknoloji team is ready to hear your project and offer a tailored roadmap.',
|
||||
),
|
||||
'Ücretsiz Danışmanlık Alın' =>
|
||||
array (
|
||||
'en' => 'Get a Free Consultation',
|
||||
'de' => 'Get a Free Consultation',
|
||||
'se' => 'Get a Free Consultation',
|
||||
'ar' => 'Get a Free Consultation',
|
||||
'ru' => 'Get a Free Consultation',
|
||||
),
|
||||
'Trunçgil Teknoloji uygulama geliştirme' =>
|
||||
array (
|
||||
'en' => 'Trunçgil Teknoloji app development',
|
||||
'de' => 'Trunçgil Teknoloji app development',
|
||||
'se' => 'Trunçgil Teknoloji app development',
|
||||
'ar' => 'Trunçgil Teknoloji app development',
|
||||
'ru' => 'Trunçgil Teknoloji app development',
|
||||
),
|
||||
),
|
||||
];
|
||||
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'site' => [
|
||||
'Güvenilir teknoloji ve iş ortaklarımızla birlikte daha güçlü çözümler sunuyoruz.' => [
|
||||
'en' => 'Together with reliable technology and business partners, we deliver stronger solutions.',
|
||||
'de' => 'Gemeinsam mit zuverlässigen Technologie- und Geschäftspartnern bieten wir stärkere Lösungen an.',
|
||||
'se' => 'Tillsammans med pålitliga teknik- och affärspartners levererar vi starkare lösningar.',
|
||||
'ar' => 'مع شركائنا الموثوقين في التكنولوجيا والأعمال، نقدم حلولًا أقوى.',
|
||||
'ru' => 'Вместе с надежными технологическими и бизнес-партнерами мы предлагаем более сильные решения.',
|
||||
],
|
||||
'Çözüm Ortağı' => [
|
||||
'en' => 'Solution Partner',
|
||||
'de' => 'Lösungspartner',
|
||||
'se' => 'Lösningspartner',
|
||||
'ar' => 'شريك الحلول',
|
||||
'ru' => 'Партнер по решениям',
|
||||
],
|
||||
'güvenilir iş ortağı' => [
|
||||
'en' => 'trusted business partner',
|
||||
'de' => 'zuverlässiger Geschäftspartner',
|
||||
'se' => 'pålitlig affärspartner',
|
||||
'ar' => 'شريك أعمال موثوق',
|
||||
'ru' => 'надежный деловой партнер',
|
||||
],
|
||||
'Güçlü ekosistem, güçlü çözümler' => [
|
||||
'en' => 'Strong ecosystem, strong solutions',
|
||||
'de' => 'Starkes Ökosystem, starke Lösungen',
|
||||
'se' => 'Starkt ekosystem, starka lösningar',
|
||||
'ar' => 'نظام بيئي قوي، حلول قوية',
|
||||
'ru' => 'Сильная экосистема - сильные решения',
|
||||
],
|
||||
'İş birliği için iletişime geçin' => [
|
||||
'en' => 'Get in touch for collaboration',
|
||||
'de' => 'Kontaktieren Sie uns für eine Zusammenarbeit',
|
||||
'se' => 'Kontakta oss för samarbete',
|
||||
'ar' => 'تواصلوا معنا للتعاون',
|
||||
'ru' => 'Свяжитесь с нами для сотрудничества',
|
||||
],
|
||||
'Web sitesini ziyaret et' => [
|
||||
'en' => 'Visit website',
|
||||
'de' => 'Website besuchen',
|
||||
'se' => 'Besök webbplatsen',
|
||||
'ar' => 'زيارة الموقع الإلكتروني',
|
||||
'ru' => 'Посетить сайт',
|
||||
],
|
||||
'Henüz çözüm ortağı eklenmemiş.' => [
|
||||
'en' => 'No solution partner has been added yet.',
|
||||
'de' => 'Es wurde noch kein Lösungspartner hinzugefügt.',
|
||||
'se' => 'Ingen lösningspartner har lagts till ännu.',
|
||||
'ar' => 'لم يتم إضافة أي شريك حلول بعد.',
|
||||
'ru' => 'Пока не добавлен ни один партнер по решениям.',
|
||||
],
|
||||
'Yönetim panelinden çözüm ortaklarınızı ekleyebilirsiniz.' => [
|
||||
'en' => 'You can add your solution partners from the admin panel.',
|
||||
'de' => 'Sie können Ihre Lösungspartner im Admin-Panel hinzufügen.',
|
||||
'se' => 'Du kan lägga till dina lösningspartners från adminpanelen.',
|
||||
'ar' => 'يمكنكم إضافة شركاء الحلول من لوحة الإدارة.',
|
||||
'ru' => 'Вы можете добавить партнеров по решениям из панели администратора.',
|
||||
],
|
||||
'Sektörün önde gelen markalarıyla stratejik iş birlikleri kurarak müşterilerimize en iyi teknoloji ve hizmet deneyimini sunuyoruz.' => [
|
||||
'en' => 'By building strategic partnerships with leading brands in the industry, we offer our customers the best technology and service experience.',
|
||||
'de' => 'Durch strategische Partnerschaften mit führenden Marken der Branche bieten wir unseren Kunden die beste Technologie- und Serviceerfahrung.',
|
||||
'se' => 'Genom strategiska samarbeten med ledande varumärken i branschen erbjuder vi våra kunder bästa möjliga teknik- och tjänsteupplevelse.',
|
||||
'ar' => 'من خلال شراكات استراتيجية مع العلامات الرائدة في القطاع، نقدم لعملائنا أفضل تجربة تقنية وخدمية.',
|
||||
'ru' => 'Выстраивая стратегическое сотрудничество с ведущими брендами отрасли, мы предоставляем клиентам лучший технологический и сервисный опыт.',
|
||||
],
|
||||
'Ödeme yöntemi' => [
|
||||
'en' => 'Payment method',
|
||||
'de' => 'Zahlungsmethode',
|
||||
'se' => 'Betalningsmetod',
|
||||
'ar' => 'طريقة الدفع',
|
||||
'ru' => 'Способ оплаты',
|
||||
],
|
||||
'Kart ile Ödeme' => [
|
||||
'en' => 'Pay by Card',
|
||||
'de' => 'Zahlung per Karte',
|
||||
'se' => 'Betala med kort',
|
||||
'ar' => 'الدفع بالبطاقة',
|
||||
'ru' => 'Оплата картой',
|
||||
],
|
||||
'Havale / EFT (IBAN)' => [
|
||||
'en' => 'Bank Transfer / EFT (IBAN)',
|
||||
'de' => 'Überweisung / EFT (IBAN)',
|
||||
'se' => 'Banköverföring / EFT (IBAN)',
|
||||
'ar' => 'حوالة بنكية / EFT (IBAN)',
|
||||
'ru' => 'Банковский перевод / EFT (IBAN)',
|
||||
],
|
||||
'Hesap Sahibi' => [
|
||||
'en' => 'Account Holder',
|
||||
'de' => 'Kontoinhaber',
|
||||
'se' => 'Kontoinnehavare',
|
||||
'ar' => 'صاحب الحساب',
|
||||
'ru' => 'Владелец счета',
|
||||
],
|
||||
'Kopyala' => [
|
||||
'en' => 'Copy',
|
||||
'de' => 'Kopieren',
|
||||
'se' => 'Kopiera',
|
||||
'ar' => 'نسخ',
|
||||
'ru' => 'Копировать',
|
||||
],
|
||||
'Kopyalandı' => [
|
||||
'en' => 'Copied',
|
||||
'de' => 'Kopiert',
|
||||
'se' => 'Kopierad',
|
||||
'ar' => 'تم النسخ',
|
||||
'ru' => 'Скопировано',
|
||||
],
|
||||
'IBAN kopyala' => [
|
||||
'en' => 'Copy IBAN',
|
||||
'de' => 'IBAN kopieren',
|
||||
'se' => 'Kopiera IBAN',
|
||||
'ar' => 'نسخ IBAN',
|
||||
'ru' => 'Скопировать IBAN',
|
||||
],
|
||||
'Henüz banka hesabı eklenmemiş.' => [
|
||||
'en' => 'No bank account has been added yet.',
|
||||
'de' => 'Es wurde noch kein Bankkonto hinzugefügt.',
|
||||
'se' => 'Inget bankkonto har lagts till ännu.',
|
||||
'ar' => 'لم يتم إضافة أي حساب بنكي بعد.',
|
||||
'ru' => 'Пока не добавлен ни один банковский счет.',
|
||||
],
|
||||
'Yönetim panelinden banka hesaplarınızı ekleyebilirsiniz.' => [
|
||||
'en' => 'You can add your bank accounts from the admin panel.',
|
||||
'de' => 'Sie können Ihre Bankkonten im Admin-Panel hinzufügen.',
|
||||
'se' => 'Du kan lägga till dina bankkonton från adminpanelen.',
|
||||
'ar' => 'يمكنكم إضافة حساباتكم البنكية من لوحة الإدارة.',
|
||||
'ru' => 'Вы можете добавить банковские счета из панели администратора.',
|
||||
],
|
||||
'Künye' => [
|
||||
'en' => 'Imprint',
|
||||
'de' => 'Impressum',
|
||||
'se' => 'Imprint',
|
||||
'ar' => 'البيانات القانونية',
|
||||
'ru' => 'Юридическая информация',
|
||||
],
|
||||
'Resmi şirket bilgileri ve yasal sorumluluklar.' => [
|
||||
'en' => 'Official company information and legal responsibilities.',
|
||||
'de' => 'Offizielle Firmeninformationen und rechtliche Verantwortung.',
|
||||
'se' => 'Officiell företagsinformation och juridiskt ansvar.',
|
||||
'ar' => 'معلومات الشركة الرسمية والمسؤوليات القانونية.',
|
||||
'ru' => 'Официальная информация о компании и юридическая ответственность.',
|
||||
],
|
||||
'Yasal Künye Bilgileri' => [
|
||||
'en' => 'Legal Imprint Details',
|
||||
'de' => 'Rechtliche Angaben (Impressum)',
|
||||
'se' => 'Juridisk information',
|
||||
'ar' => 'تفاصيل البيانات القانونية',
|
||||
'ru' => 'Юридические выходные данные',
|
||||
],
|
||||
'Ticaret Sicil No' => [
|
||||
'en' => 'Trade Registry No',
|
||||
'de' => 'Handelsregisternummer',
|
||||
'se' => 'Organisationsnummer',
|
||||
'ar' => 'رقم السجل التجاري',
|
||||
'ru' => 'Регистрационный номер компании',
|
||||
],
|
||||
'Ticaret Odası' => [
|
||||
'en' => 'Chamber of Commerce',
|
||||
'de' => 'Handelskammer',
|
||||
'se' => 'Handelskammare',
|
||||
'ar' => 'الغرفة التجارية',
|
||||
'ru' => 'Торгово-промышленная палата',
|
||||
],
|
||||
'Gaziantep Ticaret Odası (GTO)' => [
|
||||
'en' => 'Gaziantep Chamber of Commerce (GTO)',
|
||||
'de' => 'Handelskammer Gaziantep (GTO)',
|
||||
'se' => 'Gazianteps Handelskammare (GTO)',
|
||||
'ar' => 'غرفة تجارة غازي عنتاب (GTO)',
|
||||
'ru' => 'Торгово-промышленная палата Газиантепа (GTO)',
|
||||
],
|
||||
'Sorumlu Müdür' => [
|
||||
'en' => 'Responsible Manager',
|
||||
'de' => 'Verantwortlicher Geschäftsführer',
|
||||
'se' => 'Ansvarig utgivare',
|
||||
'ar' => 'المدير المسؤول',
|
||||
'ru' => 'Ответственный менеджер',
|
||||
],
|
||||
'Yer Sağlayıcı' => [
|
||||
'en' => 'Hosting Provider',
|
||||
'de' => 'Hosting-Anbieter',
|
||||
'se' => 'Webbhotell',
|
||||
'ar' => 'مزود خدمة الاستضافة',
|
||||
'ru' => 'Хостинг-провайдер',
|
||||
],
|
||||
'KEP Adresi' => [
|
||||
'en' => 'Registered Email (KEP)',
|
||||
'de' => 'Registrierte E-Mail (KEP)',
|
||||
'se' => 'Registrerad e-post (KEP)',
|
||||
'ar' => 'البريد الإلكتروني المسجل (KEP)',
|
||||
'ru' => 'Зарегистрированный E-mail (KEP)',
|
||||
],
|
||||
'İletişim ve Resmi Bilgiler' => [
|
||||
'en' => 'Contact & Official Info',
|
||||
'de' => 'Kontakt & Offizielle Informationen',
|
||||
'se' => 'Kontakt & Officiell information',
|
||||
'ar' => 'الاتصال والمعلومات الرسمية',
|
||||
'ru' => 'Контактная и официальная информация',
|
||||
],
|
||||
'Kurumsal Bilgiler' => [
|
||||
'en' => 'Corporate Information',
|
||||
'de' => 'Unternehmensinformationen',
|
||||
'se' => 'Företagsinformation',
|
||||
'ar' => 'معلومات الشركة',
|
||||
'ru' => 'Информация о компании',
|
||||
],
|
||||
'Yetkili Temsilci' => [
|
||||
'en' => 'Authorized Representative',
|
||||
'de' => 'Vertretungsberechtigte Person',
|
||||
'se' => 'Behörig företrädare',
|
||||
'ar' => 'الممثل المفوض',
|
||||
'ru' => 'Уполномоченный представитель',
|
||||
],
|
||||
'Telefon' => [
|
||||
'en' => 'Phone',
|
||||
'de' => 'Telefon',
|
||||
'se' => 'Telefon',
|
||||
'ar' => 'الهاتف',
|
||||
'ru' => 'Телефон',
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,420 @@
|
||||
<?php
|
||||
|
||||
return array (
|
||||
'pages' =>
|
||||
array (
|
||||
'kurumsal' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Корпоративный',
|
||||
),
|
||||
),
|
||||
'hakkimizda' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'О нас',
|
||||
),
|
||||
),
|
||||
'urunlerimiz' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Наши продукты',
|
||||
),
|
||||
),
|
||||
'blog' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Блог',
|
||||
),
|
||||
),
|
||||
'iletisim' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Контакты',
|
||||
),
|
||||
),
|
||||
'neler-yapariz' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Что мы делаем',
|
||||
),
|
||||
),
|
||||
'kariyer' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Карьера',
|
||||
),
|
||||
),
|
||||
'kitaplarimiz' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Наши книги',
|
||||
),
|
||||
),
|
||||
'odullerimiz' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Наши награды',
|
||||
),
|
||||
),
|
||||
'logolarimiz1' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Наши логотипы',
|
||||
),
|
||||
),
|
||||
'musteri-gorusleri' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Отзывы клиентов',
|
||||
),
|
||||
),
|
||||
'banka-bilgilerimiz' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Банковские реквизиты',
|
||||
),
|
||||
),
|
||||
'cozum-ortaklari' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Партнеры по решениям',
|
||||
),
|
||||
),
|
||||
'truncgil-akademi' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Академия Trunçgil',
|
||||
),
|
||||
),
|
||||
'online-odeme' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Онлайн-оплата',
|
||||
),
|
||||
),
|
||||
'uygulama-gelistirme' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'ru' => 'Разработка приложений',
|
||||
),
|
||||
),
|
||||
'home' =>
|
||||
array (
|
||||
'title' =>
|
||||
array (
|
||||
'de' => 'Startseite',
|
||||
'ru' => 'Главная',
|
||||
),
|
||||
),
|
||||
),
|
||||
'site' =>
|
||||
array (
|
||||
'Hayatı <span class="hero-underline yellow">kolaylaştıran</span> çözümler sunuyoruz' =>
|
||||
array (
|
||||
'ru' => 'Мы предлагаем решения, которые <span class="hero-underline yellow">упрощают</span> жизнь',
|
||||
),
|
||||
'Hayatı kolaylaştıran uygulamaları insan odaklı, akılcı ve sade bir biçimde gerçekleştirmek için var gücümüzle çalışıyoruz.' =>
|
||||
array (
|
||||
'ru' => 'Мы работаем изо всех сил, чтобы создавать приложения, делающие жизнь проще — человекоориентированным, рациональным и простым способом.',
|
||||
),
|
||||
'Daha Fazla Oku' =>
|
||||
array (
|
||||
'ru' => 'Read More',
|
||||
),
|
||||
'Online Ödeme' =>
|
||||
array (
|
||||
'ru' => 'Online Payment',
|
||||
),
|
||||
'Yapay Zeka' =>
|
||||
array (
|
||||
'ru' => 'Artificial Intelligence',
|
||||
),
|
||||
'Yapay zeka teknolojilerini kullanarak işlerinizi otomatikleştirebiliriz.' =>
|
||||
array (
|
||||
'ru' => 'We can automate your workflows using artificial intelligence technologies.',
|
||||
),
|
||||
'Daha fazla bilgi edin' =>
|
||||
array (
|
||||
'ru' => 'Learn more',
|
||||
),
|
||||
'Web Uygulamaları' =>
|
||||
array (
|
||||
'ru' => 'Web Applications',
|
||||
),
|
||||
'Web uygulamaları geliştirmek için gerekli olan tüm hizmetleri sunuyoruz.' =>
|
||||
array (
|
||||
'ru' => 'We provide all the services needed to develop web applications.',
|
||||
),
|
||||
'Müzik Prodüksiyon' =>
|
||||
array (
|
||||
'ru' => 'Music Production',
|
||||
),
|
||||
'Müzik prodüksiyonu, film müzikleri, kurumsal müzik çalışmaları ve benzeri alanlarda hizmet veriyoruz.' =>
|
||||
array (
|
||||
'ru' => 'We provide services in music production, film scores, corporate music projects, and similar fields.',
|
||||
),
|
||||
'Uygulama Geliştirme' =>
|
||||
array (
|
||||
'ru' => 'App Development',
|
||||
),
|
||||
'Android, iOS, MacOS, Windows uygulamaları geliştiriyoruz.' =>
|
||||
array (
|
||||
'ru' => 'We develop Android, iOS, macOS, and Windows applications.',
|
||||
),
|
||||
'Neden Trunçgil?' =>
|
||||
array (
|
||||
'ru' => 'Why Trunçgil?',
|
||||
),
|
||||
'Müşterilerimizin Trunçgil\'i tercih etmesinin birkaç' =>
|
||||
array (
|
||||
'ru' => 'A few reasons why our customers choose',
|
||||
),
|
||||
'nedeni' =>
|
||||
array (
|
||||
'ru' => 'Trunçgil',
|
||||
),
|
||||
'burada.' =>
|
||||
array (
|
||||
'ru' => 'are here.',
|
||||
),
|
||||
'Fikir Toplama' =>
|
||||
array (
|
||||
'ru' => 'Idea Gathering',
|
||||
),
|
||||
'Fikirlerinizi toplar ve organize ederiz, süreçleri yönetiriz.' =>
|
||||
array (
|
||||
'ru' => 'We collect and organize your ideas and manage the processes.',
|
||||
),
|
||||
'Veri Analizi' =>
|
||||
array (
|
||||
'ru' => 'Data Analysis',
|
||||
),
|
||||
'Verilerinizi analiz eder ve anlamlı sonuçlar çıkarırız.' =>
|
||||
array (
|
||||
'ru' => 'We analyze your data and derive meaningful results.',
|
||||
),
|
||||
'Ürünü Tamamla' =>
|
||||
array (
|
||||
'ru' => 'Complete the Product',
|
||||
),
|
||||
'Ürününüzü son haline getirir ve teslim ederiz.' =>
|
||||
array (
|
||||
'ru' => 'We finalize your product and deliver it.',
|
||||
),
|
||||
'Daha Fazla Bilgi' =>
|
||||
array (
|
||||
'ru' => 'More Information',
|
||||
),
|
||||
'Mutlu Müşteriler' =>
|
||||
array (
|
||||
'ru' => 'Happy Customers',
|
||||
),
|
||||
'Bizi müşterilerimizden dinleyin.' =>
|
||||
array (
|
||||
'ru' => 'Hear about us from our customers.',
|
||||
),
|
||||
'Müşteri görüşleri yakında burada olacak.' =>
|
||||
array (
|
||||
'ru' => 'Customer reviews will be here soon.',
|
||||
),
|
||||
'Bizi Neden Tercih Etmelisiniz' =>
|
||||
array (
|
||||
'ru' => 'Why You Should Choose Us',
|
||||
),
|
||||
'Siz değerli müşterilerimizin bizi tercih etmesinin yalnızca birkaç nedeni.' =>
|
||||
array (
|
||||
'ru' => 'Just a few reasons why our valued customers choose us.',
|
||||
),
|
||||
'Yaratıcılık' =>
|
||||
array (
|
||||
'ru' => 'Creativity',
|
||||
),
|
||||
'Seçkin içeriklerle daima fark yaratan fikirler.' =>
|
||||
array (
|
||||
'ru' => 'Ideas that always make a difference with outstanding content.',
|
||||
),
|
||||
'Yenilikçi Düşünce' =>
|
||||
array (
|
||||
'ru' => 'Innovative Thinking',
|
||||
),
|
||||
'Geleceği hedefleyen modern ve özgün yaklaşımlar.' =>
|
||||
array (
|
||||
'ru' => 'Modern and original approaches aimed at the future.',
|
||||
),
|
||||
'Hızlı Çözümler' =>
|
||||
array (
|
||||
'ru' => 'Rapid Solutions',
|
||||
),
|
||||
'İhtiyaç anında anında sunulan pratik cevaplar.' =>
|
||||
array (
|
||||
'ru' => 'Practical answers delivered instantly when you need them.',
|
||||
),
|
||||
'Üst Düzey Destek' =>
|
||||
array (
|
||||
'ru' => 'Top-Notch Support',
|
||||
),
|
||||
'Çözümlerimiz' =>
|
||||
array (
|
||||
'ru' => 'Our Solutions',
|
||||
),
|
||||
'İşletmenizin ihtiyaçlarını biz karşılarken siz arkanıza yaslanın ve rahatlayın.' =>
|
||||
array (
|
||||
'ru' => 'Lean back and relax while we meet your business needs.',
|
||||
),
|
||||
'Müşteri Memnuniyeti' =>
|
||||
array (
|
||||
'ru' => 'Customer Satisfaction',
|
||||
),
|
||||
'Verimlilik artışı' =>
|
||||
array (
|
||||
'ru' => 'Efficiency increase',
|
||||
),
|
||||
'İletişim' =>
|
||||
array (
|
||||
'ru' => 'Contact',
|
||||
),
|
||||
'Bir sorunuz mu var? Bizimle iletişime geçmekten çekinmeyin.' =>
|
||||
array (
|
||||
'ru' => 'Have a question? Do not hesitate to contact us.',
|
||||
),
|
||||
'Adres' =>
|
||||
array (
|
||||
'ru' => 'Address',
|
||||
),
|
||||
'Telefon' =>
|
||||
array (
|
||||
'ru' => 'Phone',
|
||||
),
|
||||
'E-mail' =>
|
||||
array (
|
||||
'ru' => 'E-mail',
|
||||
),
|
||||
'Web Tasarım' =>
|
||||
array (
|
||||
'ru' => 'Web Design',
|
||||
),
|
||||
'İşinizi bir üst seviyeye taşıyacak özgün ve modern web tasarımları üretiyoruz.' =>
|
||||
array (
|
||||
'ru' => 'We create unique and modern web designs that will take your business to the next level.',
|
||||
),
|
||||
'Detaylı Bilgi' =>
|
||||
array (
|
||||
'ru' => 'Detailed Information',
|
||||
),
|
||||
'Mobil Tasarım' =>
|
||||
array (
|
||||
'ru' => 'Mobile Design',
|
||||
),
|
||||
'Mobil cihazlara uygun yenilikçi ve kullanıcı dostu tasarımlar geliştiriyoruz.' =>
|
||||
array (
|
||||
'ru' => 'We develop innovative and user-friendly designs optimized for mobile devices.',
|
||||
),
|
||||
'Ne Yapıyoruz?' =>
|
||||
array (
|
||||
'ru' => 'What We Do',
|
||||
),
|
||||
'Sunduğumuz tüm hizmetler, iş gereksinimlerinizi en iyi şekilde karşılamak için özel olarak tasarlandı.' =>
|
||||
array (
|
||||
'ru' => 'All the services we offer are specially designed to best meet your business requirements.',
|
||||
),
|
||||
'Daha Fazla Detay' =>
|
||||
array (
|
||||
'ru' => 'More Details',
|
||||
),
|
||||
'Ürün ve Hizmetlerimiz' =>
|
||||
array (
|
||||
'ru' => 'Our Products & Services',
|
||||
),
|
||||
'Neler Yapıyoruz?' =>
|
||||
array (
|
||||
'ru' => 'What Are We Doing?',
|
||||
),
|
||||
'İnsan odaklı <br class="hidden md:block xl:!hidden lg:!hidden"><span class="!text-[#e31e24] ">akılcı ve sade</span>' =>
|
||||
array (
|
||||
'ru' => 'Human-centered, <br class="hidden md:block xl:!hidden lg:!hidden"><span class="!text-[#e31e24] ">rational and simple</span>',
|
||||
),
|
||||
'Hayatı kolaylaştırabilecek uygulamaları insan odaklı, akılcı, sade ve estetik <br class="hidden md:block xl:!hidden lg:!hidden"> bir biçimde gerçekleştirmek için var gücümüzle çalışıyoruz.' =>
|
||||
array (
|
||||
'ru' => 'We work tirelessly to deliver applications that can make life easier in a human-centered, rational, simple, and aesthetic way.',
|
||||
),
|
||||
'Projeleriniz için en yaratıcı fikirleri topluyor ve organize ediyoruz. Müşterilerimizle yakın iş birliği içinde çalışarak, ihtiyaçlarınızı anlıyor ve en uygun çözümleri geliştiriyoruz. Deneyimli ekibimiz, her projeye özel yaklaşımlarla süreçleri yönetiyor ve başarılı sonuçlar elde ediyor.' =>
|
||||
array (
|
||||
'ru' => 'We gather and organize the most creative ideas for your projects. Working closely with our clients, we understand your needs and develop the most suitable solutions. Our experienced team manages processes with tailored approaches and achieves successful results.',
|
||||
),
|
||||
'Yaratıcı fikirler toplama ve analiz etme.' =>
|
||||
array (
|
||||
'ru' => 'Collecting and analyzing creative ideas.',
|
||||
),
|
||||
'Müşteri ihtiyaçlarını anlama ve çözüm geliştirme.' =>
|
||||
array (
|
||||
'ru' => 'Understanding customer needs and developing solutions.',
|
||||
),
|
||||
'Profesyonel ekip ile süreç yönetimi ve takip.' =>
|
||||
array (
|
||||
'ru' => 'Process management and tracking with a professional team.',
|
||||
),
|
||||
'Kurumsal verilerinizi derinlemesine analiz ediyor ve anlamlı içgörüler çıkarıyoruz. Modern analitik araçlarımız ve uzman ekibimiz sayesinde, iş süreçlerinizi optimize edebilir ve karar verme süreçlerinizi güçlendirebilirsiniz. Verilerinizden maksimum değeri elde etmenizi sağlıyoruz.' =>
|
||||
array (
|
||||
'ru' => 'We deeply analyze your corporate data and extract meaningful insights. With our modern analytics tools and expert team, you can optimize your business processes and strengthen your decision-making. We help you get maximum value from your data.',
|
||||
),
|
||||
'Derinlemesine veri analizi ve raporlama.' =>
|
||||
array (
|
||||
'ru' => 'In-depth data analysis and reporting.',
|
||||
),
|
||||
'İş süreçlerini optimize etme ve iyileştirme.' =>
|
||||
array (
|
||||
'ru' => 'Optimizing and improving business processes.',
|
||||
),
|
||||
'Stratejik karar verme için içgörü sağlama.' =>
|
||||
array (
|
||||
'ru' => 'Providing insights for strategic decision-making.',
|
||||
),
|
||||
'Geliştirme sürecinin son aşamasında, ürününüzü mükemmel hale getiriyor ve teslim ediyoruz. Kalite kontrolünden kullanıcı testlerine, dokümantasyondan eğitime kadar tüm detayları eksiksiz bir şekilde tamamlıyoruz. Müşterilerimizin memnuniyeti bizim önceliğimizdir.' =>
|
||||
array (
|
||||
'ru' => 'In the final stage of development, we perfect your product and deliver it. From quality control to user testing, from documentation to training, we complete every detail flawlessly. Customer satisfaction is our priority.',
|
||||
),
|
||||
'Kapsamlı kalite kontrolü ve test süreçleri.' =>
|
||||
array (
|
||||
'ru' => 'Comprehensive quality control and testing processes.',
|
||||
),
|
||||
'Detaylı dokümantasyon ve kullanıcı eğitimi.' =>
|
||||
array (
|
||||
'ru' => 'Detailed documentation and user training.',
|
||||
),
|
||||
'Zamanında teslimat ve sürekli destek hizmeti.' =>
|
||||
array (
|
||||
'ru' => 'On-time delivery and continuous support service.',
|
||||
),
|
||||
'Her adımda yanınızda olan kusursuz bir hizmet.' =>
|
||||
array (
|
||||
'ru' => 'Flawless service by your side at every step.',
|
||||
),
|
||||
'Ekibimiz, markanız için uçtan uca dijital çözümler sunar. Akıllı teknolojiler ve kullanıcı deneyimini ön planda tutarak, işinize değer katıyoruz.' =>
|
||||
array (
|
||||
'ru' => 'Our team delivers end-to-end digital solutions for your brand. We add value to your business by prioritizing smart technologies and user experience.',
|
||||
),
|
||||
'Siz sadece işinize odaklanın, biz dijital dönüşüm süreçlerinizi yönetelim.
|
||||
Teknoloji ve yazılım odaklı bir güç olarak, işletmenizin dijital çağa tam uyum sağlaması için uçtan uca inovatif çözümler geliştiriyoruz. İhtiyaçlarınıza özel yazılım mimarileri ve modern altyapılar kurarak, manuel süreçlerinizi tam otomatik ve verimli sistemlere dönüştürüyoruz. Sektörel tecrübemizle markanızın teknolojik dönüşümünü gerçekleştirirken, sürdürülebilir başarı ve ölçeklenebilir büyüme için en ileri yazılım teknolojilerini işinizin merkezine yerleştiriyoruz.' =>
|
||||
array (
|
||||
'ru' => 'Focus on your business while we manage your digital transformation.
|
||||
As a technology and software-driven team, we develop end-to-end innovative solutions to help your business fully adapt to the digital age. We build custom software architectures and modern infrastructures, turning manual processes into fully automated and efficient systems. With our industry experience, we lead your brand\'s technological transformation while placing cutting-edge software technologies at the heart of your business for sustainable success and scalable growth.',
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'site' => [
|
||||
'Hayatı <span class="hero-underline yellow">kolaylaştıran</span> çözümler sunuyoruz' => [
|
||||
'en' => 'We deliver solutions that <span class="hero-underline yellow">simplify</span> life',
|
||||
'de' => 'Wir liefern Lösungen, die das Leben <span class="hero-underline yellow">einfacher</span> machen',
|
||||
'se' => 'Vi levererar lösningar som <span class="hero-underline yellow">förenklar</span> livet',
|
||||
'ar' => 'نقدّم حلولاً <span class="hero-underline yellow">تُسهّل</span> الحياة',
|
||||
],
|
||||
'Hayatı kolaylaştıran uygulamaları insan odaklı, akılcı ve sade bir biçimde gerçekleştirmek için var gücümüzle çalışıyoruz.' => [
|
||||
'en' => 'We work tirelessly to deliver applications that make life easier in a human-centered, rational, and simple way.',
|
||||
'de' => 'Wir arbeiten mit aller Kraft daran, Anwendungen umzusetzen, die das Leben auf menschengerechte, rationale und einfache Weise erleichtern.',
|
||||
'se' => 'Vi arbetar med full kraft för att skapa applikationer som gör livet enklare på ett människocentrerat, rationellt och enkelt sätt.',
|
||||
'ar' => 'نعمل بكل قوتنا لتقديم تطبيقات تجعل الحياة أسهل بطريقة موجهة للإنسان وعقلانية وبسيطة.',
|
||||
],
|
||||
'Daha Fazla Oku' => [
|
||||
'en' => 'Read More',
|
||||
'de' => 'Mehr lesen',
|
||||
'se' => 'Läs mer',
|
||||
'ar' => 'اقرأ المزيد',
|
||||
],
|
||||
'Online Ödeme' => [
|
||||
'en' => 'Online Payment',
|
||||
'de' => 'Online-Zahlung',
|
||||
'se' => 'Onlinebetalning',
|
||||
'ar' => 'الدفع عبر الإنترنت',
|
||||
],
|
||||
'Yapay Zeka' => [
|
||||
'en' => 'Artificial Intelligence',
|
||||
'de' => 'Künstliche Intelligenz',
|
||||
'se' => 'Artificiell intelligens',
|
||||
'ar' => 'الذكاء الاصطناعي',
|
||||
],
|
||||
'Yapay zeka teknolojilerini kullanarak işlerinizi otomatikleştirebiliriz.' => [
|
||||
'en' => 'We can automate your workflows using artificial intelligence technologies.',
|
||||
'de' => 'Mit KI-Technologien können wir Ihre Prozesse automatisieren.',
|
||||
'se' => 'Vi kan automatisera dina arbetsflöden med hjälp av AI-teknik.',
|
||||
'ar' => 'يمكننا أتمتة أعمالك باستخدام تقنيات الذكاء الاصطناعي.',
|
||||
],
|
||||
'Daha fazla bilgi edin' => [
|
||||
'en' => 'Learn more',
|
||||
'de' => 'Mehr erfahren',
|
||||
'se' => 'Läs mer',
|
||||
'ar' => 'اعرف المزيد',
|
||||
],
|
||||
'Web Uygulamaları' => [
|
||||
'en' => 'Web Applications',
|
||||
'de' => 'Webanwendungen',
|
||||
'se' => 'Webbapplikationer',
|
||||
'ar' => 'تطبيقات الويب',
|
||||
],
|
||||
'Web uygulamaları geliştirmek için gerekli olan tüm hizmetleri sunuyoruz.' => [
|
||||
'en' => 'We provide all the services needed to develop web applications.',
|
||||
'de' => 'Wir bieten alle Dienstleistungen, die für die Entwicklung von Webanwendungen erforderlich sind.',
|
||||
'se' => 'Vi erbjuder alla tjänster som behövs för att utveckla webbapplikationer.',
|
||||
'ar' => 'نقدّم جميع الخدمات اللازمة لتطوير تطبيقات الويب.',
|
||||
],
|
||||
'Müzik Prodüksiyon' => [
|
||||
'en' => 'Music Production',
|
||||
'de' => 'Musikproduktion',
|
||||
'se' => 'Musikproduktion',
|
||||
'ar' => 'إنتاج موسيقي',
|
||||
],
|
||||
'Müzik prodüksiyonu, film müzikleri, kurumsal müzik çalışmaları ve benzeri alanlarda hizmet veriyoruz.' => [
|
||||
'en' => 'We provide services in music production, film scores, corporate music projects, and similar fields.',
|
||||
'de' => 'Wir bieten Dienstleistungen in Musikproduktion, Filmmusik, Unternehmensmusik und ähnlichen Bereichen.',
|
||||
'se' => 'Vi erbjuder tjänster inom musikproduktion, filmmusik, företagsmusik och liknande områden.',
|
||||
'ar' => 'نقدّم خدمات في الإنتاج الموسيقي وموسيقى الأفلام والموسيقى المؤسسية ومجالات مشابهة.',
|
||||
],
|
||||
'Uygulama Geliştirme' => [
|
||||
'en' => 'App Development',
|
||||
'de' => 'App-Entwicklung',
|
||||
'se' => 'Apputveckling',
|
||||
'ar' => 'تطوير التطبيقات',
|
||||
],
|
||||
'Android, iOS, MacOS, Windows uygulamaları geliştiriyoruz.' => [
|
||||
'en' => 'We develop Android, iOS, macOS, and Windows applications.',
|
||||
'de' => 'Wir entwickeln Android-, iOS-, macOS- und Windows-Anwendungen.',
|
||||
'se' => 'Vi utvecklar applikationer för Android, iOS, macOS och Windows.',
|
||||
'ar' => 'نطوّر تطبيقات Android وiOS وmacOS وWindows.',
|
||||
],
|
||||
'Neden Trunçgil?' => [
|
||||
'en' => 'Why Trunçgil?',
|
||||
'de' => 'Warum Trunçgil?',
|
||||
'se' => 'Varför Trunçgil?',
|
||||
'ar' => 'لماذا Trunçgil؟',
|
||||
],
|
||||
'Müşterilerimizin Trunçgil\'i tercih etmesinin birkaç' => [
|
||||
'en' => 'A few reasons why our customers choose',
|
||||
'de' => 'Einige Gründe, warum unsere Kunden',
|
||||
'se' => 'Några anledningar till varför våra kunder väljer',
|
||||
'ar' => 'بعض الأسباب التي تجعل عملاءنا يختارون',
|
||||
],
|
||||
'nedeni' => [
|
||||
'en' => 'Trunçgil',
|
||||
'de' => 'Trunçgil',
|
||||
'se' => 'Trunçgil',
|
||||
'ar' => 'Trunçgil',
|
||||
],
|
||||
'burada.' => [
|
||||
'en' => 'are here.',
|
||||
'de' => 'wählen, finden Sie hier.',
|
||||
'se' => 'finns här.',
|
||||
'ar' => 'موجودة هنا.',
|
||||
],
|
||||
'Fikir Toplama' => [
|
||||
'en' => 'Idea Gathering',
|
||||
'de' => 'Ideensammlung',
|
||||
'se' => 'Idéinsamling',
|
||||
'ar' => 'جمع الأفكار',
|
||||
],
|
||||
'Fikirlerinizi toplar ve organize ederiz, süreçleri yönetiriz.' => [
|
||||
'en' => 'We collect and organize your ideas and manage the processes.',
|
||||
'de' => 'Wir sammeln und organisieren Ihre Ideen und steuern die Prozesse.',
|
||||
'se' => 'Vi samlar och organiserar dina idéer och hanterar processerna.',
|
||||
'ar' => 'نجمع أفكارك وننظمها وندير العمليات.',
|
||||
],
|
||||
'Veri Analizi' => [
|
||||
'en' => 'Data Analysis',
|
||||
'de' => 'Datenanalyse',
|
||||
'se' => 'Dataanalys',
|
||||
'ar' => 'تحليل البيانات',
|
||||
],
|
||||
'Verilerinizi analiz eder ve anlamlı sonuçlar çıkarırız.' => [
|
||||
'en' => 'We analyze your data and derive meaningful results.',
|
||||
'de' => 'Wir analysieren Ihre Daten und gewinnen aussagekräftige Ergebnisse.',
|
||||
'se' => 'Vi analyserar dina data och drar meningsfulla slutsatser.',
|
||||
'ar' => 'نحلل بياناتك ونستخلص نتائج ذات معنى.',
|
||||
],
|
||||
'Ürünü Tamamla' => [
|
||||
'en' => 'Complete the Product',
|
||||
'de' => 'Produkt abschließen',
|
||||
'se' => 'Slutför produkten',
|
||||
'ar' => 'إكمال المنتج',
|
||||
],
|
||||
'Ürününüzü son haline getirir ve teslim ederiz.' => [
|
||||
'en' => 'We finalize your product and deliver it.',
|
||||
'de' => 'Wir bringen Ihr Produkt in die endgültige Form und liefern es aus.',
|
||||
'se' => 'Vi färdigställer din produkt och levererar den.',
|
||||
'ar' => 'نُنهي منتجك ونسلّمه.',
|
||||
],
|
||||
'Daha Fazla Bilgi' => [
|
||||
'en' => 'More Information',
|
||||
'de' => 'Weitere Informationen',
|
||||
'se' => 'Mer information',
|
||||
'ar' => 'مزيد من المعلومات',
|
||||
],
|
||||
'Mutlu Müşteriler' => [
|
||||
'en' => 'Happy Customers',
|
||||
'de' => 'Zufriedene Kunden',
|
||||
'se' => 'Nöjda kunder',
|
||||
'ar' => 'عملاء سعداء',
|
||||
],
|
||||
'Bizi müşterilerimizden dinleyin.' => [
|
||||
'en' => 'Hear about us from our customers.',
|
||||
'de' => 'Hören Sie von uns aus erster Hand von unseren Kunden.',
|
||||
'se' => 'Hör om oss från våra kunder.',
|
||||
'ar' => 'استمع إلينا من عملائنا.',
|
||||
],
|
||||
'Müşteri görüşleri yakında burada olacak.' => [
|
||||
'en' => 'Customer reviews will be here soon.',
|
||||
'de' => 'Kundenmeinungen werden hier bald verfügbar sein.',
|
||||
'se' => 'Kundrecensioner kommer snart att finnas här.',
|
||||
'ar' => 'ستتوفر آراء العملاء هنا قريبًا.',
|
||||
],
|
||||
'Bizi Neden Tercih Etmelisiniz' => [
|
||||
'en' => 'Why You Should Choose Us',
|
||||
'de' => 'Warum Sie uns wählen sollten',
|
||||
'se' => 'Varför du bör välja oss',
|
||||
'ar' => 'لماذا يجب أن تختارنا',
|
||||
],
|
||||
'Siz değerli müşterilerimizin bizi tercih etmesinin yalnızca birkaç nedeni.' => [
|
||||
'en' => 'Just a few reasons why our valued customers choose us.',
|
||||
'de' => 'Nur einige Gründe, warum unsere geschätzten Kunden uns wählen.',
|
||||
'se' => 'Bara några anledningar till varför våra uppskattade kunder väljer oss.',
|
||||
'ar' => 'بعض الأسباب التي تجعل عملاءنا الكرام يختاروننا.',
|
||||
],
|
||||
'Yaratıcılık' => [
|
||||
'en' => 'Creativity',
|
||||
'de' => 'Kreativität',
|
||||
'se' => 'Kreativitet',
|
||||
'ar' => 'الإبداع',
|
||||
],
|
||||
'Seçkin içeriklerle daima fark yaratan fikirler.' => [
|
||||
'en' => 'Ideas that always make a difference with outstanding content.',
|
||||
'de' => 'Ideen, die mit herausragenden Inhalten immer einen Unterschied machen.',
|
||||
'se' => 'Idéer som alltid gör skillnad med utmärkt innehåll.',
|
||||
'ar' => 'أفكار تُحدث فرقًا دائمًا بمحتوى متميز.',
|
||||
],
|
||||
'Yenilikçi Düşünce' => [
|
||||
'en' => 'Innovative Thinking',
|
||||
'de' => 'Innovatives Denken',
|
||||
'se' => 'Innovativt tänkande',
|
||||
'ar' => 'التفكير الابتكاري',
|
||||
],
|
||||
'Geleceği hedefleyen modern ve özgün yaklaşımlar.' => [
|
||||
'en' => 'Modern and original approaches aimed at the future.',
|
||||
'de' => 'Moderne und originelle Ansätze mit Blick auf die Zukunft.',
|
||||
'se' => 'Moderna och originella tillvägagångssätt med fokus på framtiden.',
|
||||
'ar' => 'نهج حديثة وأصيلة تستهدف المستقبل.',
|
||||
],
|
||||
'Hızlı Çözümler' => [
|
||||
'en' => 'Rapid Solutions',
|
||||
'de' => 'Schnelle Lösungen',
|
||||
'se' => 'Snabba lösningar',
|
||||
'ar' => 'حلول سريعة',
|
||||
],
|
||||
'İhtiyaç anında anında sunulan pratik cevaplar.' => [
|
||||
'en' => 'Practical answers delivered instantly when you need them.',
|
||||
'de' => 'Praktische Antworten, die im Bedarfsfall sofort bereitstehen.',
|
||||
'se' => 'Praktiska svar som levereras direkt när du behöver dem.',
|
||||
'ar' => 'إجابات عملية تُقدَّم فورًا عند الحاجة.',
|
||||
],
|
||||
'Üst Düzey Destek' => [
|
||||
'en' => 'Top-Notch Support',
|
||||
'de' => 'Erstklassiger Support',
|
||||
'se' => 'Support i toppklass',
|
||||
'ar' => 'دعم على أعلى مستوى',
|
||||
],
|
||||
'Çözümlerimiz' => [
|
||||
'en' => 'Our Solutions',
|
||||
'de' => 'Unsere Lösungen',
|
||||
'se' => 'Våra lösningar',
|
||||
'ar' => 'حلولنا',
|
||||
],
|
||||
'İşletmenizin ihtiyaçlarını biz karşılarken siz arkanıza yaslanın ve rahatlayın.' => [
|
||||
'en' => 'Lean back and relax while we meet your business needs.',
|
||||
'de' => 'Lehnen Sie sich zurück und entspannen Sie, während wir Ihre Geschäftsanforderungen erfüllen.',
|
||||
'se' => 'Luta dig tillbaka och slappna av medan vi tillgodoser ditt företags behov.',
|
||||
'ar' => 'استرخِ بينما نلبّي احتياجات عملك.',
|
||||
],
|
||||
'Müşteri Memnuniyeti' => [
|
||||
'en' => 'Customer Satisfaction',
|
||||
'de' => 'Kundenzufriedenheit',
|
||||
'se' => 'Kundnöjdhet',
|
||||
'ar' => 'رضا العملاء',
|
||||
],
|
||||
'Verimlilik artışı' => [
|
||||
'en' => 'Efficiency increase',
|
||||
'de' => 'Effizienzsteigerung',
|
||||
'se' => 'Effektivitetsökning',
|
||||
'ar' => 'زيادة الكفاءة',
|
||||
],
|
||||
'İletişim' => [
|
||||
'en' => 'Contact',
|
||||
'de' => 'Kontakt',
|
||||
'se' => 'Kontakt',
|
||||
'ar' => 'تواصل',
|
||||
],
|
||||
'Bir sorunuz mu var? Bizimle iletişime geçmekten çekinmeyin.' => [
|
||||
'en' => 'Have a question? Do not hesitate to contact us.',
|
||||
'de' => 'Haben Sie eine Frage? Zögern Sie nicht, uns zu kontaktieren.',
|
||||
'se' => 'Har du en fråga? Tveka inte att kontakta oss.',
|
||||
'ar' => 'هل لديك سؤال؟ لا تتردد في التواصل معنا.',
|
||||
],
|
||||
'Adres' => [
|
||||
'en' => 'Address',
|
||||
'de' => 'Adresse',
|
||||
'se' => 'Adress',
|
||||
'ar' => 'العنوان',
|
||||
],
|
||||
'Telefon' => [
|
||||
'en' => 'Phone',
|
||||
'de' => 'Telefon',
|
||||
'se' => 'Telefon',
|
||||
'ar' => 'الهاتف',
|
||||
],
|
||||
'E-mail' => [
|
||||
'en' => 'E-mail',
|
||||
'de' => 'E-Mail',
|
||||
'se' => 'E-post',
|
||||
'ar' => 'البريد الإلكتروني',
|
||||
],
|
||||
'Web Tasarım' => [
|
||||
'en' => 'Web Design',
|
||||
'de' => 'Webdesign',
|
||||
'se' => 'Webbdesign',
|
||||
'ar' => 'تصميم الويب',
|
||||
],
|
||||
'İşinizi bir üst seviyeye taşıyacak özgün ve modern web tasarımları üretiyoruz.' => [
|
||||
'en' => 'We create unique and modern web designs that will take your business to the next level.',
|
||||
'de' => 'Wir erstellen einzigartige und moderne Webdesigns, die Ihr Unternehmen auf die nächste Stufe bringen.',
|
||||
'se' => 'Vi skapar unika och moderna webbdesigner som tar ditt företag till nästa nivå.',
|
||||
'ar' => 'نُنتج تصاميم ويب فريدة وحديثة ترفع عملك إلى المستوى التالي.',
|
||||
],
|
||||
'Detaylı Bilgi' => [
|
||||
'en' => 'Detailed Information',
|
||||
'de' => 'Detaillierte Informationen',
|
||||
'se' => 'Detaljerad information',
|
||||
'ar' => 'معلومات مفصلة',
|
||||
],
|
||||
'Mobil Tasarım' => [
|
||||
'en' => 'Mobile Design',
|
||||
'de' => 'Mobiles Design',
|
||||
'se' => 'Mobil design',
|
||||
'ar' => 'تصميم الجوال',
|
||||
],
|
||||
'Mobil cihazlara uygun yenilikçi ve kullanıcı dostu tasarımlar geliştiriyoruz.' => [
|
||||
'en' => 'We develop innovative and user-friendly designs optimized for mobile devices.',
|
||||
'de' => 'Wir entwickeln innovative und benutzerfreundliche Designs für mobile Geräte.',
|
||||
'se' => 'Vi utvecklar innovativa och användarvänliga designer för mobila enheter.',
|
||||
'ar' => 'نطوّر تصاميم مبتكرة وسهلة الاستخدام مناسبة للأجهزة المحمولة.',
|
||||
],
|
||||
'Ne Yapıyoruz?' => [
|
||||
'en' => 'What We Do',
|
||||
'de' => 'Was wir tun',
|
||||
'se' => 'Vad vi gör',
|
||||
'ar' => 'ماذا نفعل',
|
||||
],
|
||||
'Sunduğumuz tüm hizmetler, iş gereksinimlerinizi en iyi şekilde karşılamak için özel olarak tasarlandı.' => [
|
||||
'en' => 'All the services we offer are specially designed to best meet your business requirements.',
|
||||
'de' => 'Alle unsere Dienstleistungen sind speziell darauf ausgelegt, Ihre Geschäftsanforderungen bestmöglich zu erfüllen.',
|
||||
'se' => 'Alla tjänster vi erbjuder är särskilt utformade för att bäst möta ditt företags behov.',
|
||||
'ar' => 'جميع خدماتنا مصممة خصيصًا لتلبية متطلبات عملك بأفضل شكل.',
|
||||
],
|
||||
'Daha Fazla Detay' => [
|
||||
'en' => 'More Details',
|
||||
'de' => 'Weitere Details',
|
||||
'se' => 'Mer detaljer',
|
||||
'ar' => 'مزيد من التفاصيل',
|
||||
],
|
||||
'Ürün ve Hizmetlerimiz' => [
|
||||
'en' => 'Our Products & Services',
|
||||
'de' => 'Unsere Produkte & Dienstleistungen',
|
||||
'se' => 'Våra produkter och tjänster',
|
||||
'ar' => 'منتجاتنا وخدماتنا',
|
||||
],
|
||||
'Neler Yapıyoruz?' => [
|
||||
'en' => 'What Are We Doing?',
|
||||
'de' => 'Was machen wir?',
|
||||
'se' => 'Vad gör vi?',
|
||||
'ar' => 'ماذا نفعل؟',
|
||||
],
|
||||
'İnsan odaklı <br class="hidden md:block xl:!hidden lg:!hidden"><span class="!text-[#e31e24] ">akılcı ve sade</span>' => [
|
||||
'en' => 'Human-centered, <br class="hidden md:block xl:!hidden lg:!hidden"><span class="!text-[#e31e24] ">rational and simple</span>',
|
||||
'de' => 'Menschenzentriert, <br class="hidden md:block xl:!hidden lg:!hidden"><span class="!text-[#e31e24] ">rational und einfach</span>',
|
||||
'se' => 'Människocentrerat, <br class="hidden md:block xl:!hidden lg:!hidden"><span class="!text-[#e31e24] ">rationellt och enkelt</span>',
|
||||
'ar' => 'موجه للإنسان، <br class="hidden md:block xl:!hidden lg:!hidden"><span class="!text-[#e31e24] ">عقلاني وبسيط</span>',
|
||||
],
|
||||
'Hayatı kolaylaştırabilecek uygulamaları insan odaklı, akılcı, sade ve estetik <br class="hidden md:block xl:!hidden lg:!hidden"> bir biçimde gerçekleştirmek için var gücümüzle çalışıyoruz.' => [
|
||||
'en' => 'We work tirelessly to deliver applications that can make life easier in a human-centered, rational, simple, and aesthetic way.',
|
||||
'de' => 'Wir arbeiten mit aller Kraft daran, Anwendungen umzusetzen, die das Leben auf menschengerechte, rationale, einfache und ästhetische Weise erleichtern.',
|
||||
'se' => 'Vi arbetar med full kraft för att skapa applikationer som kan göra livet enklare på ett människocentrerat, rationellt, enkelt och estetiskt sätt.',
|
||||
'ar' => 'نعمل بكل قوتنا لتقديم تطبيقات قادرة على تسهيل الحياة بطريقة موجهة للإنسان وعقلانية وبسيطة وجمالية.',
|
||||
],
|
||||
'Projeleriniz için en yaratıcı fikirleri topluyor ve organize ediyoruz. Müşterilerimizle yakın iş birliği içinde çalışarak, ihtiyaçlarınızı anlıyor ve en uygun çözümleri geliştiriyoruz. Deneyimli ekibimiz, her projeye özel yaklaşımlarla süreçleri yönetiyor ve başarılı sonuçlar elde ediyor.' => [
|
||||
'en' => 'We gather and organize the most creative ideas for your projects. Working closely with our clients, we understand your needs and develop the most suitable solutions. Our experienced team manages processes with tailored approaches and achieves successful results.',
|
||||
'de' => 'Wir sammeln und organisieren die kreativsten Ideen für Ihre Projekte. In enger Zusammenarbeit mit unseren Kunden verstehen wir Ihre Bedürfnisse und entwickeln die passendsten Lösungen. Unser erfahrenes Team steuert Prozesse mit maßgeschneiderten Ansätzen und erzielt erfolgreiche Ergebnisse.',
|
||||
'se' => 'Vi samlar och organiserar de mest kreativa idéerna för dina projekt. I nära samarbete med våra kunder förstår vi dina behov och utvecklar de mest lämpliga lösningarna. Vårt erfarna team hanterar processer med skräddarsydda tillvägagångssätt och uppnår framgångsrika resultat.',
|
||||
'ar' => 'نجمع وننظم أكثر الأفكار إبداعًا لمشاريعك. من خلال العمل الوثيق مع عملائنا، نفهم احتياجاتكم ونطوّر الحلول الأنسب. يدير فريقنا ذو الخبرة العمليات بأساليب مخصصة ويحقق نتائج ناجحة.',
|
||||
],
|
||||
'Yaratıcı fikirler toplama ve analiz etme.' => [
|
||||
'en' => 'Collecting and analyzing creative ideas.',
|
||||
'de' => 'Sammeln und Analysieren kreativer Ideen.',
|
||||
'se' => 'Insamling och analys av kreativa idéer.',
|
||||
'ar' => 'جمع وتحليل الأفكار الإبداعية.',
|
||||
],
|
||||
'Müşteri ihtiyaçlarını anlama ve çözüm geliştirme.' => [
|
||||
'en' => 'Understanding customer needs and developing solutions.',
|
||||
'de' => 'Verstehen der Kundenbedürfnisse und Entwickeln von Lösungen.',
|
||||
'se' => 'Förstå kundbehov och utveckla lösningar.',
|
||||
'ar' => 'فهم احتياجات العملاء وتطوير الحلول.',
|
||||
],
|
||||
'Profesyonel ekip ile süreç yönetimi ve takip.' => [
|
||||
'en' => 'Process management and tracking with a professional team.',
|
||||
'de' => 'Prozessmanagement und Nachverfolgung mit einem professionellen Team.',
|
||||
'se' => 'Processhantering och uppföljning med ett professionellt team.',
|
||||
'ar' => 'إدارة العمليات والمتابعة مع فريق محترف.',
|
||||
],
|
||||
'Kurumsal verilerinizi derinlemesine analiz ediyor ve anlamlı içgörüler çıkarıyoruz. Modern analitik araçlarımız ve uzman ekibimiz sayesinde, iş süreçlerinizi optimize edebilir ve karar verme süreçlerinizi güçlendirebilirsiniz. Verilerinizden maksimum değeri elde etmenizi sağlıyoruz.' => [
|
||||
'en' => 'We deeply analyze your corporate data and extract meaningful insights. With our modern analytics tools and expert team, you can optimize your business processes and strengthen your decision-making. We help you get maximum value from your data.',
|
||||
'de' => 'Wir analysieren Ihre Unternehmensdaten gründlich und gewinnen aussagekräftige Erkenntnisse. Mit unseren modernen Analysetools und unserem Expertenteam können Sie Ihre Geschäftsprozesse optimieren und Ihre Entscheidungsfindung stärken. Wir helfen Ihnen, maximalen Wert aus Ihren Daten zu ziehen.',
|
||||
'se' => 'Vi analyserar dina företagsdata på djupet och extraherar meningsfulla insikter. Med våra moderna analysverktyg och expertteam kan du optimera dina affärsprocesser och stärka ditt beslutsfattande. Vi hjälper dig att få maximalt värde från dina data.',
|
||||
'ar' => 'نحلل بيانات شركتك بعمق ونستخرج رؤى ذات معنى. بفضل أدوات التحليل الحديثة وفريقنا الخبير، يمكنك تحسين عمليات عملك وتعزيز اتخاذ القرار. نساعدك على الحصول على أقصى قيمة من بياناتك.',
|
||||
],
|
||||
'Derinlemesine veri analizi ve raporlama.' => [
|
||||
'en' => 'In-depth data analysis and reporting.',
|
||||
'de' => 'Tiefgehende Datenanalyse und Berichterstattung.',
|
||||
'se' => 'Djupgående dataanalys och rapportering.',
|
||||
'ar' => 'تحليل بيانات متعمق وإعداد التقارير.',
|
||||
],
|
||||
'İş süreçlerini optimize etme ve iyileştirme.' => [
|
||||
'en' => 'Optimizing and improving business processes.',
|
||||
'de' => 'Optimierung und Verbesserung von Geschäftsprozessen.',
|
||||
'se' => 'Optimera och förbättra affärsprocesser.',
|
||||
'ar' => 'تحسين وتطوير عمليات الأعمال.',
|
||||
],
|
||||
'Stratejik karar verme için içgörü sağlama.' => [
|
||||
'en' => 'Providing insights for strategic decision-making.',
|
||||
'de' => 'Bereitstellung von Erkenntnissen für strategische Entscheidungen.',
|
||||
'se' => 'Tillhandahålla insikter för strategiskt beslutsfattande.',
|
||||
'ar' => 'تقديم رؤى لاتخاذ القرارات الاستراتيجية.',
|
||||
],
|
||||
'Geliştirme sürecinin son aşamasında, ürününüzü mükemmel hale getiriyor ve teslim ediyoruz. Kalite kontrolünden kullanıcı testlerine, dokümantasyondan eğitime kadar tüm detayları eksiksiz bir şekilde tamamlıyoruz. Müşterilerimizin memnuniyeti bizim önceliğimizdir.' => [
|
||||
'en' => 'In the final stage of development, we perfect your product and deliver it. From quality control to user testing, from documentation to training, we complete every detail flawlessly. Customer satisfaction is our priority.',
|
||||
'de' => 'In der letzten Entwicklungsphase perfektionieren wir Ihr Produkt und liefern es aus. Von der Qualitätskontrolle über Benutzertests bis hin zu Dokumentation und Schulung vervollständigen wir jedes Detail lückenlos. Die Kundenzufriedenheit ist unsere Priorität.',
|
||||
'se' => 'I det sista utvecklingssteget perfektionerar vi din produkt och levererar den. Från kvalitetskontroll till användartester, från dokumentation till utbildning slutför vi varje detalj felfritt. Kundnöjdhet är vår prioritet.',
|
||||
'ar' => 'في المرحلة الأخيرة من التطوير، نُكمل منتجك ونسلّمه. من مراقبة الجودة إلى اختبار المستخدمين، ومن التوثيق إلى التدريب، نُنجز كل التفاصيل بإتقان. رضا العملاء هو أولويتنا.',
|
||||
],
|
||||
'Kapsamlı kalite kontrolü ve test süreçleri.' => [
|
||||
'en' => 'Comprehensive quality control and testing processes.',
|
||||
'de' => 'Umfassende Qualitätskontrolle und Testprozesse.',
|
||||
'se' => 'Omfattande kvalitetskontroll och testprocesser.',
|
||||
'ar' => 'عمليات مراقبة جودة واختبار شاملة.',
|
||||
],
|
||||
'Detaylı dokümantasyon ve kullanıcı eğitimi.' => [
|
||||
'en' => 'Detailed documentation and user training.',
|
||||
'de' => 'Detaillierte Dokumentation und Benutzerschulung.',
|
||||
'se' => 'Detaljerad dokumentation och användarutbildning.',
|
||||
'ar' => 'توثيق مفصل وتدريب المستخدمين.',
|
||||
],
|
||||
'Zamanında teslimat ve sürekli destek hizmeti.' => [
|
||||
'en' => 'On-time delivery and continuous support service.',
|
||||
'de' => 'Pünktliche Lieferung und kontinuierlicher Support.',
|
||||
'se' => 'Leverans i tid och kontinuerlig support.',
|
||||
'ar' => 'تسليم في الوقت المحدد وخدمة دعم مستمرة.',
|
||||
],
|
||||
'Her adımda yanınızda olan kusursuz bir hizmet.' => [
|
||||
'en' => 'Flawless service by your side at every step.',
|
||||
'de' => 'Makelloser Service an Ihrer Seite bei jedem Schritt.',
|
||||
'se' => 'Felfri service vid din sida i varje steg.',
|
||||
'ar' => 'خدمة متقنة إلى جانبك في كل خطوة.',
|
||||
],
|
||||
'Ekibimiz, markanız için uçtan uca dijital çözümler sunar. Akıllı teknolojiler ve kullanıcı deneyimini ön planda tutarak, işinize değer katıyoruz.' => [
|
||||
'en' => 'Our team delivers end-to-end digital solutions for your brand. We add value to your business by prioritizing smart technologies and user experience.',
|
||||
'de' => 'Unser Team bietet End-to-End-Digitalisierungslösungen für Ihre Marke. Mit smarten Technologien und Fokus auf Nutzererlebnis schaffen wir Mehrwert für Ihr Unternehmen.',
|
||||
'se' => 'Vårt team levererar digitala helhetslösningar för ditt varumärke. Vi skapar värde för ditt företag genom att prioritera smart teknik och användarupplevelse.',
|
||||
'ar' => 'يقدّم فريقنا حلولًا رقمية متكاملة لعلامتك التجارية. نضيف قيمة لعملك من خلال إعطاء الأولوية للتقنيات الذكية وتجربة المستخدم.',
|
||||
],
|
||||
"Siz sadece işinize odaklanın, biz dijital dönüşüm süreçlerinizi yönetelim.\nTeknoloji ve yazılım odaklı bir güç olarak, işletmenizin dijital çağa tam uyum sağlaması için uçtan uca inovatif çözümler geliştiriyoruz. İhtiyaçlarınıza özel yazılım mimarileri ve modern altyapılar kurarak, manuel süreçlerinizi tam otomatik ve verimli sistemlere dönüştürüyoruz. Sektörel tecrübemizle markanızın teknolojik dönüşümünü gerçekleştirirken, sürdürülebilir başarı ve ölçeklenebilir büyüme için en ileri yazılım teknolojilerini işinizin merkezine yerleştiriyoruz." => [
|
||||
'en' => "Focus on your business while we manage your digital transformation.\nAs a technology and software-driven team, we develop end-to-end innovative solutions to help your business fully adapt to the digital age. We build custom software architectures and modern infrastructures, turning manual processes into fully automated and efficient systems. With our industry experience, we lead your brand's technological transformation while placing cutting-edge software technologies at the heart of your business for sustainable success and scalable growth.",
|
||||
'de' => "Konzentrieren Sie sich auf Ihr Geschäft, während wir Ihre digitale Transformation steuern.\nAls technologie- und softwareorientiertes Team entwickeln wir End-to-End-Innovationslösungen, damit Ihr Unternehmen sich vollständig an das digitale Zeitalter anpasst. Wir erstellen maßgeschneiderte Softwarearchitekturen und moderne Infrastrukturen und wandeln manuelle Prozesse in vollautomatische und effiziente Systeme um. Mit unserer Branchenerfahrung gestalten wir die technologische Transformation Ihrer Marke und setzen modernste Softwaretechnologien in den Mittelpunkt Ihres Unternehmens für nachhaltigen Erfolg und skalierbares Wachstum.",
|
||||
'se' => "Fokusera på ditt företag medan vi hanterar din digitala transformation.\nSom ett teknik- och mjukvarudrivet team utvecklar vi innovativa helhetslösningar så att ditt företag kan anpassa sig fullt ut till den digitala eran. Vi bygger skräddarsydda programvaruarkitekturer och modern infrastruktur och omvandlar manuella processer till helt automatiserade och effektiva system. Med vår branscherfarenhet driver vi ditt varumärkes teknologiska transformation och placerar den senaste mjukvarutekniken i centrum av ditt företag för hållbar framgång och skalbar tillväxt.",
|
||||
'ar' => "ركّز على عملك بينما ندير عملية التحول الرقمي لديك.\nبصفتنا فريقًا يركز على التكنولوجيا والبرمجيات، نطوّر حلولًا مبتكرة متكاملة لمساعدة عملك على التكيف الكامل مع العصر الرقمي. نبني بنى برمجية مخصصة وبنى تحتية حديثة، ونحوّل العمليات اليدوية إلى أنظمة آلية وفعالة بالكامل. بخبرتنا القطاعية، نقود التحول التكنولوجي لعلامتك التجارية ونضع أحدث تقنيات البرمجيات في قلب عملك لتحقيق نجاح مستدام ونمو قابل للتوسع.",
|
||||
],
|
||||
],
|
||||
'pages' => [
|
||||
2 => ['title' => ['en' => 'Corporate', 'de' => 'Unternehmen', 'se' => 'Företag', 'ar' => 'الشركة']],
|
||||
3 => ['title' => ['en' => 'About Us', 'de' => 'Über uns', 'se' => 'Om oss', 'ar' => 'معلومات عنا']],
|
||||
5 => ['title' => ['en' => 'Products', 'de' => 'Produkte', 'se' => 'Produkter', 'ar' => 'المنتجات']],
|
||||
6 => ['title' => ['en' => 'Blog', 'de' => 'Blog', 'se' => 'Blogg', 'ar' => 'المدونة']],
|
||||
7 => ['title' => ['en' => 'Contact', 'de' => 'Kontakt', 'se' => 'Kontakt', 'ar' => 'تواصل']],
|
||||
8 => ['title' => ['en' => 'What We Do', 'de' => 'Was wir tun', 'se' => 'Vad vi gör', 'ar' => 'ماذا نفعل']],
|
||||
9 => ['title' => ['en' => 'Career', 'de' => 'Karriere', 'se' => 'Karriär', 'ar' => 'الوظائف']],
|
||||
11 => ['title' => ['en' => 'Our Books', 'de' => 'Unsere Bücher', 'se' => 'Våra böcker', 'ar' => 'كتبنا']],
|
||||
12 => ['title' => ['en' => 'Our Awards', 'de' => 'Unsere Auszeichnungen', 'se' => 'Våra utmärkelser', 'ar' => 'جوائزنا']],
|
||||
13 => ['title' => ['en' => 'Our Logos', 'de' => 'Unsere Logos', 'se' => 'Våra logotyper', 'ar' => 'شعاراتنا']],
|
||||
14 => ['title' => ['en' => 'Customer Reviews', 'de' => 'Kundenmeinungen', 'se' => 'Kundrecensioner', 'ar' => 'آراء العملاء']],
|
||||
15 => ['title' => ['en' => 'Bank Details', 'de' => 'Bankverbindung', 'se' => 'Bankuppgifter', 'ar' => 'معلومات البنك']],
|
||||
16 => ['title' => ['en' => 'Solution Partners', 'de' => 'Lösungspartner', 'se' => 'Lösningspartners', 'ar' => 'شركاء الحل']],
|
||||
17 => ['title' => ['en' => 'Trunçgil Academy', 'de' => 'Trunçgil Akademie', 'se' => 'Trunçgil Academy', 'ar' => 'أكاديمية Trunçgil']],
|
||||
18 => ['title' => ['en' => 'Online Payment', 'de' => 'Online-Zahlung', 'se' => 'Onlinebetalning', 'ar' => 'الدفع عبر الإنترنت']],
|
||||
20 => ['title' => ['en' => 'App Development', 'de' => 'App-Entwicklung', 'se' => 'Apputveckling', 'ar' => 'تطوير التطبيقات']],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'site' => [
|
||||
'Trunçgil Teknoloji' => [
|
||||
'en' => 'Trunçgil Technology',
|
||||
'de' => 'Trunçgil Technologie',
|
||||
'se' => 'Trunçgil Teknologi',
|
||||
'ar' => 'ترونتشغيل تكنولوجي',
|
||||
'ru' => 'Trunçgil Технологии',
|
||||
],
|
||||
'Tüm hakları saklıdır.' => [
|
||||
'en' => 'All rights reserved.',
|
||||
'de' => 'Alle Rechte vorbehalten.',
|
||||
'se' => 'Alla rättigheter förbehållna.',
|
||||
'ar' => 'جميع الحقوق محفوظة.',
|
||||
'ru' => 'Все права защищены.',
|
||||
],
|
||||
'Hikayemiz' => [
|
||||
'en' => 'Our Story',
|
||||
'de' => 'Unsere Geschichte',
|
||||
'se' => 'Vår berättelse',
|
||||
'ar' => 'قصتنا',
|
||||
'ru' => 'Наша история',
|
||||
],
|
||||
'Projelerimiz' => [
|
||||
'en' => 'Our Projects',
|
||||
'de' => 'Unsere Projekte',
|
||||
'se' => 'Våra projekt',
|
||||
'ar' => 'مشاريعنا',
|
||||
'ru' => 'Наши проекты',
|
||||
],
|
||||
'Kullanım Koşulları' => [
|
||||
'en' => 'Terms of Use',
|
||||
'de' => 'Nutzungsbedingungen',
|
||||
'se' => 'Användarvillkor',
|
||||
'ar' => 'شروط الاستخدام',
|
||||
'ru' => 'Условия использования',
|
||||
],
|
||||
'Gizlilik Politikası' => [
|
||||
'en' => 'Privacy Policy',
|
||||
'de' => 'Datenschutzerklärung',
|
||||
'se' => 'Integritetspolicy',
|
||||
'ar' => 'سياسة الخصوصية',
|
||||
'ru' => 'Политика конфиденциальности',
|
||||
],
|
||||
'Bizimle İletişime Geçin' => [
|
||||
'en' => 'Get in Touch with Us',
|
||||
'de' => 'Kontaktieren Sie uns',
|
||||
'se' => 'Kontakta oss',
|
||||
'ar' => 'تواصلوا معنا',
|
||||
'ru' => 'Свяжитесь с нами',
|
||||
],
|
||||
'Yeniliklerden haberdar olmak için bize e-posta adresinizi bırakın.' => [
|
||||
'en' => 'Leave us your email address to stay informed about updates.',
|
||||
'de' => 'Hinterlassen Sie uns Ihre E-Mail-Adresse, um über Neuigkeiten informiert zu bleiben.',
|
||||
'se' => 'Lämna din e-postadress för att hålla dig uppdaterad om nyheter.',
|
||||
'ar' => 'اتركوا لنا بريدكم الإلكتروني لتبقوا على اطلاع على المستجدات.',
|
||||
'ru' => 'Оставьте нам свой e-mail, чтобы быть в курсе новостей.',
|
||||
],
|
||||
'E-posta Adresiniz' => [
|
||||
'en' => 'Your Email Address',
|
||||
'de' => 'Ihre E-Mail-Adresse',
|
||||
'se' => 'Din e-postadress',
|
||||
'ar' => 'عنوان بريدك الإلكتروني',
|
||||
'ru' => 'Ваш адрес электронной почты',
|
||||
],
|
||||
'Abone Ol' => [
|
||||
'en' => 'Subscribe',
|
||||
'de' => 'Abonnieren',
|
||||
'se' => 'Prenumerera',
|
||||
'ar' => 'اشترك',
|
||||
'ru' => 'Подписаться',
|
||||
],
|
||||
'Bize Ulaşın' => [
|
||||
'en' => 'Reach Us',
|
||||
'de' => 'Kontaktieren Sie uns',
|
||||
'se' => 'Kontakta oss',
|
||||
'ar' => 'تواصلوا معنا',
|
||||
'ru' => 'Свяжитесь с нами',
|
||||
],
|
||||
'Mesaj Gönder' => [
|
||||
'en' => 'Send Message',
|
||||
'de' => 'Nachricht senden',
|
||||
'se' => 'Skicka meddelande',
|
||||
'ar' => 'إرسال رسالة',
|
||||
'ru' => 'Отправить сообщение',
|
||||
],
|
||||
'Adınız' => [
|
||||
'en' => 'Your Name',
|
||||
'de' => 'Ihr Name',
|
||||
'se' => 'Ditt namn',
|
||||
'ar' => 'اسمك',
|
||||
'ru' => 'Ваше имя',
|
||||
],
|
||||
'E-posta' => [
|
||||
'en' => 'Email',
|
||||
'de' => 'E-Mail',
|
||||
'se' => 'E-post',
|
||||
'ar' => 'البريد الإلكتروني',
|
||||
'ru' => 'Электронная почта',
|
||||
],
|
||||
'Mesajınız' => [
|
||||
'en' => 'Your Message',
|
||||
'de' => 'Ihre Nachricht',
|
||||
'se' => 'Ditt meddelande',
|
||||
'ar' => 'رسالتك',
|
||||
'ru' => 'Ваше сообщение',
|
||||
],
|
||||
'Bu alanlar zorunludur.' => [
|
||||
'en' => 'These fields are required.',
|
||||
'de' => 'Diese Felder sind erforderlich.',
|
||||
'se' => 'Dessa fält är obligatoriska.',
|
||||
'ar' => 'هذه الحقول مطلوبة.',
|
||||
'ru' => 'Эти поля обязательны.',
|
||||
],
|
||||
'Bu sayfa henüz içerik bulunmuyor' => [
|
||||
'en' => 'This page does not have content yet.',
|
||||
'de' => 'Diese Seite enthält noch keine Inhalte.',
|
||||
'se' => 'Den här sidan har ännu inget innehåll.',
|
||||
'ar' => 'لا يوجد محتوى في هذه الصفحة بعد.',
|
||||
'ru' => 'На этой странице пока нет контента.',
|
||||
],
|
||||
'Admin panelinden bu sayfaya içerik veya bloklar ekleyebilirsiniz.' => [
|
||||
'en' => 'You can add content or blocks to this page from the admin panel.',
|
||||
'de' => 'Sie können dieser Seite über das Admin-Panel Inhalte oder Blöcke hinzufügen.',
|
||||
'se' => 'Du kan lägga till innehåll eller block på den här sidan från adminpanelen.',
|
||||
'ar' => 'يمكنكم إضافة محتوى أو أقسام إلى هذه الصفحة من لوحة الإدارة.',
|
||||
'ru' => 'Вы можете добавить контент или блоки на эту страницу через панель администратора.',
|
||||
],
|
||||
'Kitaplarımız' => [
|
||||
'en' => 'Our Books',
|
||||
'de' => 'Unsere Bücher',
|
||||
'se' => 'Våra böcker',
|
||||
'ar' => 'كتبنا',
|
||||
'ru' => 'Наши книги',
|
||||
],
|
||||
'Geleceği şekillendiren teknolojileri, uzman kalemlerden derinlemesine öğrenin.' => [
|
||||
'en' => 'Learn in depth from experts about the technologies shaping the future.',
|
||||
'de' => 'Lernen Sie die Technologien, die die Zukunft prägen, in der Tiefe von Experten kennen.',
|
||||
'se' => 'Lär dig på djupet av experter om teknikerna som formar framtiden.',
|
||||
'ar' => 'تعرّف بعمق من الخبراء على التقنيات التي تشكّل المستقبل.',
|
||||
'ru' => 'Изучайте в деталях у экспертов технологии, формирующие будущее.',
|
||||
],
|
||||
'İncele & Satın Al' => [
|
||||
'en' => 'Review & Buy',
|
||||
'de' => 'Ansehen & Kaufen',
|
||||
'se' => 'Utforska och köp',
|
||||
'ar' => 'استعراض وشراء',
|
||||
'ru' => 'Посмотреть и купить',
|
||||
],
|
||||
'Logolarımız' => [
|
||||
'en' => 'Our Logos',
|
||||
'de' => 'Unsere Logos',
|
||||
'se' => 'Våra logotyper',
|
||||
'ar' => 'شعاراتنا',
|
||||
'ru' => 'Наши логотипы',
|
||||
],
|
||||
'Logo Varyasyonlarımız' => [
|
||||
'en' => 'Our Logo Variations',
|
||||
'de' => 'Unsere Logo-Varianten',
|
||||
'se' => 'Våra logovarianter',
|
||||
'ar' => 'تنويعات شعارنا',
|
||||
'ru' => 'Варианты нашего логотипа',
|
||||
],
|
||||
'Logomuzun Felsefesi' => [
|
||||
'en' => 'The Philosophy of Our Logo',
|
||||
'de' => 'Die Philosophie unseres Logos',
|
||||
'se' => 'Filosofin bakom vår logotyp',
|
||||
'ar' => 'فلسفة شعارنا',
|
||||
'ru' => 'Философия нашего логотипа',
|
||||
],
|
||||
'Yapay Zeka ve Derin Öğrenme ile' => [
|
||||
'en' => 'With Artificial Intelligence and Deep Learning',
|
||||
'de' => 'Mit Künstlicher Intelligenz und Deep Learning',
|
||||
'se' => 'Med artificiell intelligens och djupinlärning',
|
||||
'ar' => 'مع الذكاء الاصطناعي والتعلم العميق',
|
||||
'ru' => 'С искусственным интеллектом и глубоким обучением',
|
||||
],
|
||||
'Geleceği Şekillendirin' => [
|
||||
'en' => 'Shape the Future',
|
||||
'de' => 'Gestalten Sie die Zukunft',
|
||||
'se' => 'Forma framtiden',
|
||||
'ar' => 'شكّل المستقبل',
|
||||
'ru' => 'Формируйте будущее',
|
||||
],
|
||||
'Karmaşık verilerinizi saniyeler içinde anlamlı kararlara ve akıllı otomasyon sistemlerine dönüştürüyoruz. PyTorch ve TensorFlow motorları ile işletmenize özel Yapay Zeka çözümleri sunuyoruz.' => [
|
||||
'en' => 'We transform your complex data into meaningful decisions and smart automation systems in seconds. We offer custom Artificial Intelligence solutions for your business with PyTorch and TensorFlow engines.',
|
||||
'de' => 'Wir verwandeln Ihre komplexen Daten in Sekundenschnelle in aussagekräftige Entscheidungen und intelligente Automatisierungssysteme. Wir bieten maßgeschneiderte Lösungen für künstliche Intelligenz für Ihr Unternehmen mit PyTorch- und TensorFlow-Engines.',
|
||||
'se' => 'Vi omvandlar dina komplexa data till meningsfulla beslut och smarta automatiseringssystem på några sekunder. Vi erbjuder anpassade lösningar för artificiell intelligens för ditt företag med PyTorch- och TensorFlow-motorer.',
|
||||
'ar' => 'نحول بياناتك المعقدة إلى قرارات ذات مغزى وأنظمة أتمتة ذكية في ثوانٍ. نقدم حلولاً مخصصة للذكاء الاصطناعي لعملك مع محركات PyTorch و TensorFlow.',
|
||||
'ru' => 'Мы превращаем ваши сложные данные в значимые решения и интеллектуальные системы автоматизации за секунды. Мы предлагаем индивидуальные решения в области искусственного интеллекта для вашего бизнеса на базе движков PyTorch и TensorFlow.',
|
||||
],
|
||||
'Teknolojileri İnceleyin' => [
|
||||
'en' => 'Explore the Technologies',
|
||||
'de' => 'Technologien erkunden',
|
||||
'se' => 'Utforska teknikerna',
|
||||
'ar' => 'استكشف التقنيات',
|
||||
'ru' => 'Изучите технологии',
|
||||
],
|
||||
'Yapay Zeka ve Derin Öğrenme Geliştirme Hizmeti' => [
|
||||
'en' => 'Artificial Intelligence & Deep Learning Development Service',
|
||||
'de' => 'Entwicklungsdienst für Künstliche Intelligenz & Deep Learning',
|
||||
'se' => 'Utvecklingstjänst för artificiell intelligens och djupinlärning',
|
||||
'ar' => 'خدمة تطوير الذكاء الاصطناعي والتعلم العميق',
|
||||
'ru' => 'Услуги по разработке искусственного интеллекта и глубокого обучения',
|
||||
],
|
||||
'PyTorch ve TensorFlow ile veri modellemeden Edge AI yayınına kadar tam teşekküllü yapay zeka, LoRA fine-tuning ve makine öğrenmesi sistemleri geliştiriyoruz.' => [
|
||||
'en' => 'We develop full-fledged artificial intelligence, LoRA fine-tuning, and machine learning systems from data modeling to Edge AI deployment with PyTorch and TensorFlow.',
|
||||
'de' => 'Wir entwickeln vollwertige Systeme für künstliche Intelligenz, LoRA-Feinabstimmung und maschinelles Lernen von der Datenmodellierung bis zur Edge-KI-Bereitstellung mit PyTorch und TensorFlow.',
|
||||
'se' => 'Vi utvecklar fullfjädrade system för artificiell intelligens, LoRA-finjustering och maskininlärning från datamodellering till Edge AI-distribution med PyTorch och och TensorFlow.',
|
||||
'ar' => 'نطور أنظمة ذكاء اصطناعي متكاملة، وضبط دقيق لـ LoRA، وأنظمة تعلم آلي من نمذجة البيانات إلى نشر Edge AI باستخدام PyTorch و TensorFlow.',
|
||||
'ru' => 'Мы разрабатываем полноценные системы искусственного интеллекта, точную настройку LoRA и системы машинного обучения от моделирования данных до развертывания Edge AI с помощью PyTorch и TensorFlow.',
|
||||
],
|
||||
'Yapay Zeka Teknolojilerimiz ve Süreçlerimiz' => [
|
||||
'en' => 'Our AI Technologies and Processes',
|
||||
'de' => 'Unsere KI-Technologien und -Prozesse',
|
||||
'se' => 'Våra AI-tekniker och processer',
|
||||
'ar' => 'تقنيات وعمليات الذكاء الاصطناعي لدينا',
|
||||
'ru' => 'Наши технологии и процессы ИИ',
|
||||
],
|
||||
'Yapay zeka modellerinin arkasındaki karmaşık süreçleri, en güçlü teknolojilerle harmanlayarak işletmenize özel anahtar teslim çözümlere dönüştürüyoruz.' => [
|
||||
'en' => 'We transform the complex processes behind AI models into custom turnkey solutions for your business by blending them with the most powerful technologies.',
|
||||
'de' => 'Wir verwandeln die komplexen Prozesse hinter KI-Modellen in maßgeschneiderte schlüsselfertige Lösungen für Ihr Unternehmen, indem wir sie mit den leistungsstärksten Technologien kombinieren.',
|
||||
'se' => 'Vi omvandlar de komplexa processerna bakom AI-modeller till skräddarsydda nyckelfärdiga lösningar för ditt företag genom att blanda dem med de mest kraftfulla teknikerna.',
|
||||
'ar' => 'نحول العمليات المعقدة وراء نماذج الذكاء الاصطناعي إلى حلول مخصصة جاهزة للتشغيل لعملك من خلال دمجها مع أقوى التقنيات.',
|
||||
'ru' => 'Мы превращаем сложные процессы, стоящие за моделями ИИ, в индивидуальные готовые решения для вашего бизнеса, сочетая их с самыми мощными технологиями.',
|
||||
],
|
||||
'TensorFlow & Dağıtık Eğitim' => [
|
||||
'en' => 'TensorFlow & Distributed Training',
|
||||
'de' => 'TensorFlow & Verteiltes Training',
|
||||
'se' => 'TensorFlow och distribuerad träning',
|
||||
'ar' => 'TensorFlow والتدريب الموزع',
|
||||
'ru' => 'TensorFlow и распределенное обучение',
|
||||
],
|
||||
'`tf.data` asenkron veri hatları ve MirroredStrategy ile büyük veriyi GPU/TPU sunucularında paralel olarak hızlandırılmış biçimde eğitiyoruz.' => [
|
||||
'en' => 'We train big data asynchronously using `tf.data` pipelines and MirroredStrategy in a parallel accelerated manner on GPU/TPU servers.',
|
||||
'de' => 'Wir trainieren Big Data asynchron mit `tf.data`-Pipelines und MirroredStrategy parallel beschleunigt auf GPU/TPU-Servern.',
|
||||
'se' => 'Vi tränar big data asynkront med `tf.data`-pipelines och MirroredStrategy på ett parallellt accelererat sätt på GPU/TPU-servrar.',
|
||||
'ar' => 'نقوم بتدريب البيانات الضخمة بشكل غير متزامن باستخدام خطوط أنابيب `tf.data` و MirroredStrategy بطريقة متوازية متسارعة على خوادم GPU/TPU.',
|
||||
'ru' => 'Мы обучаем большие данные асинхронно, используя конвейеры `tf.data` и MirroredStrategy, параллельно ускоренным способом на серверах GPU/TPU.',
|
||||
],
|
||||
'PyTorch & Derin Öğrenme' => [
|
||||
'en' => 'PyTorch & Deep Learning',
|
||||
'de' => 'PyTorch & Deep Learning',
|
||||
'se' => 'PyTorch och djupinlärning',
|
||||
'ar' => 'PyTorch والتعلم العميق',
|
||||
'ru' => 'PyTorch и глубокое обучение',
|
||||
],
|
||||
'Dinamik hesaplama grafiği (autograd) ve DDP ile esnek yapay sinir ağları tasarlıyor, karmaşık modellerin eğitim döngüsünü sıfırdan yönetiyoruz.' => [
|
||||
'en' => 'We design flexible neural networks with dynamic computation graphs (autograd) and DDP, managing complex model training cycles from scratch.',
|
||||
'de' => 'Wir entwerfen flexible neuronale Netze mit dynamischen Berechnungsgraphen (autograd) und DDP und verwalten komplexe Modelltrainingszyklen von Grund auf.',
|
||||
'se' => 'Vi designar flexibla neurala nätverk med dynamiska beräkningsgrafer (autograd) och DDP, och hanterar komplexa modellträningscykler från grunden.',
|
||||
'ar' => 'نصمم شبكات عصبية مرنة باستخدام مخططات الحوسبة الديناميكية (autograd) و DDP، وندير دورات تدريب النماذج المعقدة من الصفر.',
|
||||
'ru' => 'Мы проектируем гибкие нейронные сети с динамическими вычислительными графами (autograd) и DDP, управляя циклами обучения сложных моделей с нуля.',
|
||||
],
|
||||
'LoRA & QLoRA İnce Ayar' => [
|
||||
'en' => 'LoRA & QLoRA Fine-Tuning',
|
||||
'de' => 'LoRA & QLoRA Feinabstimmung',
|
||||
'se' => 'LoRA och QLoRA-finjustering',
|
||||
'ar' => 'ضبط دقيق لـ LoRA و QLoRA',
|
||||
'ru' => 'Тонкая настройка LoRA и QLoRA',
|
||||
],
|
||||
'PEFT teknikleriyle milyarlarca parametreli açık kaynaklı LLM\'leri (Llama, Mistral) firmanızın özel verileriyle bütçe dostu biçimde eğitiyoruz.' => [
|
||||
'en' => "We train billion-parameter open-source LLMs (Llama, Mistral) with your company's private data in a budget-friendly manner using PEFT techniques.",
|
||||
'de' => 'Wir trainieren Open-Source-LLMs mit Milliarden von Parametern (Llama, Mistral) kostengünstig mit den privaten Daten Ihres Unternehmens unter Verwendung von PEFT-Techniken.',
|
||||
'se' => 'Vi tränar open-source LLM:er med miljarder parametrar (Llama, Mistral) med ditt företags privata data på ett budgetvänligt sätt med hjälp av PEFT-tekniker.',
|
||||
'ar' => 'نقوم بتدريب نماذج LLM مفتوحة المصدر ذات مليارات المعلمات (Llama، Mistral) ببيانات شركتك الخاصة بطريقة صديقة للميزانية باستخدام تقنيات PEFT.',
|
||||
'ru' => 'Мы обучаем опенсорсные LLM с миллиардами параметров (Llama, Mistral) на частных данных вашей компании экономичным способом с использованием методов PEFT.',
|
||||
],
|
||||
'Triton & Yüksek Hızlı Sunum' => [
|
||||
'en' => 'Triton & High-Speed Serving',
|
||||
'de' => 'Triton & High-Speed-Serving',
|
||||
'se' => 'Triton och höghastighetsservering',
|
||||
'ar' => 'Triton والتقديم عالي السرعة',
|
||||
'ru' => 'Triton и высокоскоростное обслуживание',
|
||||
],
|
||||
'NVIDIA Triton ve TorchServe entegrasyonuyla modellerinizi Kubernetes üzerinde, dinamik gruplama (batching) ve düşük gecikmeyle yayına alıyoruz.' => [
|
||||
'en' => 'We deploy your models on Kubernetes with dynamic batching and low latency using NVIDIA Triton and TorchServe integration.',
|
||||
'de' => 'Wir stellen Ihre Modelle auf Kubernetes mit dynamischem Batching und geringer Latenz unter Verwendung der NVIDIA Triton- und TorchServe-Integration bereit.',
|
||||
'se' => 'Vi distribuerar dina modeller på Kubernetes med dynamisk batchning och låg latens med hjälp av NVIDIA Triton- och TorchServe-integrering.',
|
||||
'ar' => 'ننشر نماذجك على Kubernetes مع تجميع ديناميكي وزمن انتقال منخفض باستخدام تكامل NVIDIA Triton و TorchServe.',
|
||||
'ru' => 'Мы развертываем ваши модели на Kubernetes с динамическим пакетированием и низкой задержкой, используя интеграцию NVIDIA Triton и TorchServe.',
|
||||
],
|
||||
'Bilgisayarlı Görü (Vision)' => [
|
||||
'en' => 'Computer Vision',
|
||||
'de' => 'Computer Vision',
|
||||
'se' => 'Datorseende (Vision)',
|
||||
'ar' => 'الرؤية الحاسوبية',
|
||||
'ru' => 'Компьютерное зрение',
|
||||
],
|
||||
'Fabrikalarda otonom kalite kontrol, nesne takibi ve YOLO mimarileri ile anlık görsel analiz gerçekleştiren akıllı gözler tasarlıyoruz.' => [
|
||||
'en' => 'We design smart eyes that perform instant visual analysis with autonomous quality control, object tracking, and YOLO architectures in factories.',
|
||||
'de' => 'Wir entwickeln intelligente Augen, die mit autonomer Qualitätskontrolle, Objektverfolgung und YOLO-Architekturen in Fabriken sofortige visuelle Analysen durchführen.',
|
||||
'se' => 'Vi designar smarta ögon som utför omedelbar visuell analys med autonom kvalitetskontroll, objektspårning och YOLO-arkitekturer i fabriker.',
|
||||
'ar' => 'نصمم عيوناً ذكية تقوم بالتحليل البصري الفوري مع مراقبة الجودة الذاتية، وتتبع الأشياء، وبنيات YOLO في المصانع.',
|
||||
'ru' => 'Мы проектируем «умные глаза», которые выполняют мгновенный визуальный анализ с автономным контролем качества, отслеживанием объектов и архитектурами YOLO на заводах.',
|
||||
],
|
||||
'Hibrit LLM & RAG Çözümleri' => [
|
||||
'en' => 'Hybrid LLM & RAG Solutions',
|
||||
'de' => 'Hybride LLM- & RAG-Lösungen',
|
||||
'se' => 'Hybrid-LLM- och RAG-lösningar',
|
||||
'ar' => 'حلول LLM الهجينة و RAG',
|
||||
'ru' => 'Гибридные решения LLM и RAG',
|
||||
],
|
||||
'Şirket içi belgelerinizi akıllı arama (Vector DB) ile birleştirerek, en son LLM teknolojileriyle entegre özel bilgi asistanları geliştiriyoruz.' => [
|
||||
'en' => 'We develop custom information assistants integrated with the latest LLM technologies by combining your internal documents with smart search (Vector DB).',
|
||||
'de' => 'Wir entwickeln maßgeschneiderte Informationsassistenten, die mit den neuesten LLM-Technologien integriert sind, indem wir Ihre internen Dokumente mit der intelligenten Suche (Vector DB) kombieren.',
|
||||
'se' => 'Vi utvecklar anpassade informationsassistenter integrerade med de senaste LLM-teknikerna genom att kombinera dina interna dokument med smart sökning (Vector DB).',
|
||||
'ar' => 'نطور مساعدين مخصصين للمعلومات متكاملين مع أحدث تقنيات LLM من خلال دمج مستنداتك الداخلية مع البحث الذكي (Vector DB).',
|
||||
'ru' => 'Мы разрабатываем индивидуальных информационных ассистентов, интегрированных с новейшими технологиями LLM, объединяя ваши внутренние документы с интеллектуальным поиском (Vector DB).',
|
||||
],
|
||||
'Eğitim Sürecinde Tam Kontrol' => [
|
||||
'en' => 'Full Control in the Training Process',
|
||||
'de' => 'Volle Kontrolle im Trainingsprozess',
|
||||
'se' => 'Full kontroll i träningsprocessen',
|
||||
'ar' => 'التحكم الكامل في عملية التدريب',
|
||||
'ru' => 'Полный контроль в процессе обучения',
|
||||
],
|
||||
'Yapay zekanın eğitim döngüsünü adım adım izliyor, her aşamayı optimize ediyoruz.' => [
|
||||
'en' => 'We monitor the AI training cycle step by step and optimize every stage.',
|
||||
'de' => 'Wir überwachen den KI-Trainingszyklus Schritt für Schritt und optimieren jede Phase.',
|
||||
'se' => 'Vi övervakar AI-träningscykeln steg för steg och optimerar varje steg.',
|
||||
'ar' => 'نراقب دورة تدريب الذكاء الاصطناعي خطوة بخطوة ونحسن كل مرحلة.',
|
||||
'ru' => 'Мы шаг за шагом отслеживаем цикл обучения ИИ и оптимизируем каждый этап.',
|
||||
],
|
||||
'Model eğitimi sadece kod yazmaktan ibaret değildir. Eğitim esnasında gradient patlamalarını, aşırı öğrenmeyi (overfitting) ve kaynak tüketimini anlık olarak izliyoruz. Şeffaf ve kontrol edilebilir yapay zeka modelleri geliştiriyoruz.' => [
|
||||
'en' => 'Model training is not just about writing code. During training, we monitor gradient explosions, overfitting, and resource consumption in real-time. We develop transparent and controllable AI models.',
|
||||
'de' => 'Modelltraining besteht nicht nur aus dem Schreiben von Code. Während des Trainings überwachen wir Gradientenexplosionen, Overfitting und Ressourcenverbrauch in Echtzeit. Wir entwickeln transparente und kontrollierbare KI-Modelle.',
|
||||
'se' => 'Modellträning handlar inte bara om att skriva kod. Under träningen övervakar vi gradientexplosioner, överanpassning (overfitting) och resursförbrukning i realtid. Vi utvecklar transparenta och kontrollerbara AI-modeller.',
|
||||
'ar' => 'التدريب على النماذج لا يقتصر فقط على كتابة التعليمات البرمجية. أثناء التدريب، نراقب انفجارات التدرج، والإفراط في التجهيز (overfitting)، واستهلاك الموارد في الوقت الفعلي. نطور نماذج ذكاء اصطناعي شفافة وقابلة للتحكم.',
|
||||
'ru' => 'Обучение моделей — это не просто написание кода. Во время обучения мы в реальном времени отслеживаем взрывы градиентов, переобучение (overfitting) и потребление ресурсов. Мы разрабатываем прозрачные и управляемые модели ИИ.',
|
||||
],
|
||||
'TensorBoard ile Görselleştirme' => [
|
||||
'en' => 'Visualization with TensorBoard',
|
||||
'de' => 'Visualisierung mit TensorBoard',
|
||||
'se' => 'Visualisering med TensorBoard',
|
||||
'ar' => 'التقييم البصري باستخدام TensorBoard',
|
||||
'ru' => 'Визуализация с помощью TensorBoard',
|
||||
],
|
||||
'Kayıp (loss) ve doğruluk (accuracy) grafiklerini anlık takip ediyoruz.' => [
|
||||
'en' => 'We track loss and accuracy graphs in real-time.',
|
||||
'de' => 'Wir verfolgen Verlust- und Genauigkeitsdiagramme in Echtzeit.',
|
||||
'se' => 'Vi spårar förlust- och noggrannhetsgrafer i realtid.',
|
||||
'ar' => 'نتتبع الرسوم البيانية للخسارة (loss) والدقة (accuracy) في الوقت الفعلي.',
|
||||
'ru' => 'Мы отслеживаем графики потерь (loss) и точности (accuracy) в реальном времени.',
|
||||
],
|
||||
'Erken Durdurma (Early Stopping)' => [
|
||||
'en' => 'Early Stopping',
|
||||
'de' => 'Frühes Anhalten (Early Stopping)',
|
||||
'se' => 'Tidigt stopp (Early Stopping)',
|
||||
'ar' => 'التوقف المبكر (Early Stopping)',
|
||||
'ru' => 'Ранняя остановка (Early Stopping)',
|
||||
],
|
||||
'Model aşırı öğrenmeye başladığı anda eğitimi otomatik kesip en iyi ağırlıkları koruyoruz.' => [
|
||||
'en' => 'We automatically stop training when the model starts overfitting and preserve the best weights.',
|
||||
'de' => 'Wir stoppen das Training automatisch, wenn das Modell mit dem Overfitting beginnt, und bewahren die besten Gewichte.',
|
||||
'se' => 'Vi avbryter träningen automatiskt när modellen börjar överanpassas och bevarar de bästa vikterna.',
|
||||
'ar' => 'نوقف التدريب تلقائياً عندما يبدأ النموذج في الإفراط في التجهيز ونحافظ على أفضل الأوزان.',
|
||||
'ru' => 'Мы автоматически останавливаем обучение, когда модель начинает переобучаться, и сохраняем лучшие веса.',
|
||||
],
|
||||
'Hiperparametre Optimizasyonu' => [
|
||||
'en' => 'Hyperparameter Optimization',
|
||||
'de' => 'Hyperparameter-Optimierung',
|
||||
'se' => 'Hyperparameteroptimering',
|
||||
'ar' => 'تحسين المعلمات الفائقة (Hyperparameter)',
|
||||
'ru' => 'Оптимизация гиперпараметров',
|
||||
],
|
||||
'Optuna ve Ray Tune kullanarak en doğru öğrenme oranını (learning rate) buluyoruz.' => [
|
||||
'en' => 'We find the most accurate learning rate using Optuna and Ray Tune.',
|
||||
'de' => 'Wir finden die genaueste Lernrate unter Verwendung von Optuna und Ray Tune.',
|
||||
'se' => 'Vi hittar den mest exakta inlärningshastigheten med hjälp av Optuna och Ray Tune.',
|
||||
'ar' => 'نجد معدل التعلم الأكثر دقة باستخدام Optuna و Ray Tune.',
|
||||
'ru' => 'Мы находим наиболее точную скорость обучения с помощью Optuna и Ray Tune.',
|
||||
],
|
||||
'Ağırlık Kaydetme (Checkpointing)' => [
|
||||
'en' => 'Model Checkpointing',
|
||||
'de' => 'Modell-Checkpointing',
|
||||
'se' => 'Modellsparning (Checkpointing)',
|
||||
'ar' => 'حفظ الأوزان (Checkpointing)',
|
||||
'ru' => 'Сохранение контрольных точек (Checkpointing)',
|
||||
],
|
||||
'Eğitim aşamalarını düzenli kaydederek kesinti durumunda kaldığımız yerden devam ettiriyoruz.' => [
|
||||
'en' => 'We save training phases regularly so we can resume from where we left off in case of interruption.',
|
||||
'de' => 'Wir speichern die Trainingsphasen regelmäßig, sodass wir im Falle einer Unterbrechung dort fortfahren können, wo wir aufgehört haben.',
|
||||
'se' => 'Vi sparar träningsfaser regelbundet så att vi kan återuppta där vi slutade vid eventuella avbrott.',
|
||||
'ar' => 'نحفظ مراحل التدريب بانتظام حتى نتمكن من الاستئناف من حيث توقفنا في حالة حدوث انقطاع.',
|
||||
'ru' => 'Мы регулярно сохраняем этапы обучения, чтобы иметь возможность возобновить работу с того места, на котором остановились, в случае прерывания.',
|
||||
],
|
||||
'Neden Trunçgil Yapay Zeka Çözümleri?' => [
|
||||
'en' => 'Why Trunçgil AI Solutions?',
|
||||
'de' => 'Warum Trunçgil KI-Lösungen?',
|
||||
'se' => 'Varför Trunçgil AI-lösningar?',
|
||||
'ar' => 'لماذا حلول Trunçgil للذكاء الاصطناعي؟',
|
||||
'ru' => 'Почему решения ИИ от Trunçgil?',
|
||||
],
|
||||
'Verilerinizin gücünü açığa çıkarıyor, işinizi geleceğe taşıyoruz.' => [
|
||||
'en' => 'We unleash the power of your data and carry your business into the future.',
|
||||
'de' => 'Wir setzen die Kraft Ihrer Daten frei und führen Ihr Unternehmen in die Zukunft.',
|
||||
'se' => 'Vi frigör kraften i dina data och tar ditt företag in i framtiden.',
|
||||
'ar' => 'نطلق العنان لقوة بياناتك وننقل عملك إلى المستقبل.',
|
||||
'ru' => 'Мы высвобождаем силу ваших данных и переносим ваш бизнес в будущее.',
|
||||
],
|
||||
'Yapay zeka çözümlerimizi tamamen işletmenizin özel ihtiyaçlarına, bütçesine ve altyapısına uyarlıyoruz. Dışa bağımlılık olmadan, yüksek performanslı ve güvenli bir dijital dönüşüm sunuyoruz.' => [
|
||||
'en' => "We adapt our AI solutions completely to your business's custom needs, budget, and infrastructure. We offer a high-performance and secure digital transformation without external dependencies.",
|
||||
'de' => 'Wir passen unsere KI-Lösungen vollständig an die individuellen Bedürfnisse, das Budget und die Infrastruktur Ihres Unternehmens an. Wir bieten eine leistungsstarke und sichere digitale Transformation ohne externe Abhängigkeiten.',
|
||||
'se' => 'Vi anpassar våra AI-lösningar helt till ditt företags specifika behov, budget och infrastruktur. Vi erbjuder en högpresterande och säker digital transformation utan externa beroenden.',
|
||||
'ar' => 'نكيف حلولنا للذكاء الاصطناعي تماماً مع احتياجات عملك الخاصة وميزانيته وبنيته التحتية. نحن نقدم تحولاً رقمياً عالي الأداء وآمناً دون أي تبعيات خارجية.',
|
||||
'ru' => 'Мы полностью адаптируем наши решения ИИ к индивидуальным потребностям, бюджету и инфраструктуре вашего бизнеса. Мы предлагаем высокопроизводительную и безопасную цифровую трансформацию без внешней зависимости.',
|
||||
],
|
||||
'Sıfır Dış Bağımlılık & Yerel Kurulum' => [
|
||||
'en' => 'Zero External Dependency & On-Premise Installation',
|
||||
'de' => 'Keine externe Abhängigkeit & On-Premise-Installation',
|
||||
'se' => 'Noll externt beroende och on-premise installation',
|
||||
'ar' => 'صفر تبعية خارجية وتثبيت محلي',
|
||||
'ru' => 'Нулевая внешняя зависимость и локальная установка',
|
||||
],
|
||||
'Düşük Gecikmeli Çıkarım Mimarisi' => [
|
||||
'en' => 'Low Latency Inference Architecture',
|
||||
'de' => 'Low-Latency-Inference-Architektur',
|
||||
'se' => 'Inferensarkitektur med låg latens',
|
||||
'ar' => 'بنية استدلال منخفضة زمن الوصول',
|
||||
'ru' => 'Архитектура вывода с низкой задержкой',
|
||||
],
|
||||
'Uç Cihazlara Taşınabilirlik (Edge AI)' => [
|
||||
'en' => 'Portability to Edge Devices (Edge AI)',
|
||||
'de' => 'Portabilität auf Edge-Geräte (Edge AI)',
|
||||
'se' => 'Portabilitet till edge-enheter (Edge AI)',
|
||||
'ar' => 'القابلية للنقل إلى الأجهزة الطرفية (Edge AI)',
|
||||
'ru' => 'Портативность на периферийные устройства (Edge AI)',
|
||||
],
|
||||
'50+ Eğitilmiş Model' => [
|
||||
'en' => '50+ Trained Models',
|
||||
'de' => '50+ Trainierte Modelle',
|
||||
'se' => '50+ tränade modeller',
|
||||
'ar' => 'أكثر من 50 نموذجاً مدرباً',
|
||||
'ru' => '50+ обученных моделей',
|
||||
],
|
||||
'Başarıyla Tamamlanan Projeler' => [
|
||||
'en' => 'Successfully Completed Projects',
|
||||
'de' => 'Erfolgreich abgeschlossene Projekte',
|
||||
'se' => 'Framgångsrikt slutförda projekt',
|
||||
'ar' => 'مشاريع اكتملت بنجاح',
|
||||
'ru' => 'Успешно завершенные проекты',
|
||||
],
|
||||
'Maliyet Azaltma' => [
|
||||
'en' => 'Cost Reduction',
|
||||
'de' => 'Kostenreduzierung',
|
||||
'se' => 'Kostnadsreduktion',
|
||||
'ar' => 'تقليل التكلفة',
|
||||
'ru' => 'Снижение затрат',
|
||||
],
|
||||
'LoRA ve Sıkıştırma ile' => [
|
||||
'en' => 'With LoRA & Compression',
|
||||
'de' => 'Mit LoRA & Komprimierung',
|
||||
'se' => 'Med LoRA och komprimering',
|
||||
'ar' => 'مع LoRA والضغط',
|
||||
'ru' => 'С LoRA и сжатием',
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,350 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'site' => [
|
||||
'neler-yapariz.page_title' => [
|
||||
'tr' => 'Neler Yaparız',
|
||||
'en' => 'What We Do',
|
||||
'de' => 'Was wir tun',
|
||||
'se' => 'Vad vi gör',
|
||||
'ar' => 'ماذا نفعل',
|
||||
'ru' => 'Что мы делаем',
|
||||
],
|
||||
'neler-yapariz.hero.title' => [
|
||||
'tr' => 'Hayatı kolaylaştıran',
|
||||
'en' => 'We deliver solutions that',
|
||||
'de' => 'Wir bieten Lösungen, die',
|
||||
'se' => 'Vi erbjuder lösningar som',
|
||||
'ar' => 'نقدّم حلولاً',
|
||||
'ru' => 'Мы предлагаем решения, которые',
|
||||
],
|
||||
'neler-yapariz.hero.title_highlight' => [
|
||||
'tr' => 'çözümler sunuyoruz',
|
||||
'en' => 'simplify life',
|
||||
'de' => 'das Leben erleichtern',
|
||||
'se' => 'förenklar livet',
|
||||
'ar' => 'تُسهّل الحياة',
|
||||
'ru' => 'упрощают жизнь',
|
||||
],
|
||||
'neler-yapariz.hero.subtitle' => [
|
||||
'tr' => 'Müşterilerimizle uzun vadeli ilişkiler kuran hayat boyu destek ve öğrenme süreçlerini benimseyen yenilikçi bir şirketiz.',
|
||||
'en' => 'We are an innovative company that builds long-term relationships with our customers and embraces lifelong support and learning processes.',
|
||||
'de' => 'Wir sind ein innovatives Unternehmen, das langfristige Beziehungen zu unseren Kunden aufbaut und lebenslange Support- sowie Lernprozesse lebt.',
|
||||
'se' => 'Vi är ett innovativt företag som bygger långsiktiga relationer med våra kunder och arbetar med livslångt stöd och lärande.',
|
||||
'ar' => 'نحن شركة مبتكرة تبني علاقات طويلة الأمد مع عملائنا وتتبنّى الدعم والتعلّم مدى الحياة.',
|
||||
'ru' => 'Мы инновационная компания, которая выстраивает долгосрочные отношения с клиентами и поддерживает непрерывные процессы поддержки и обучения.',
|
||||
],
|
||||
'neler-yapariz.hero.cta_primary' => [
|
||||
'tr' => 'İletişime Geç',
|
||||
'en' => 'Contact Us',
|
||||
'de' => 'Kontaktieren Sie uns',
|
||||
'se' => 'Kontakta oss',
|
||||
'ar' => 'تواصل معنا',
|
||||
'ru' => 'Свяжитесь с нами',
|
||||
],
|
||||
'neler-yapariz.hero.cta_secondary' => [
|
||||
'tr' => 'Ürünlerimizi Keşfet',
|
||||
'en' => 'Explore Our Products',
|
||||
'de' => 'Entdecken Sie unsere Produkte',
|
||||
'se' => 'Utforska våra produkter',
|
||||
'ar' => 'اكتشف منتجاتنا',
|
||||
'ru' => 'Откройте для себя наши продукты',
|
||||
],
|
||||
'neler-yapariz.hero.cta_contact' => [
|
||||
'tr' => 'İletişim',
|
||||
'en' => 'Contact',
|
||||
'de' => 'Kontakt',
|
||||
'se' => 'Kontakt',
|
||||
'ar' => 'تواصل',
|
||||
'ru' => 'Контакты',
|
||||
],
|
||||
'neler-yapariz.stats.completed_projects' => [
|
||||
'tr' => 'Tamamlanan Projeler',
|
||||
'en' => 'Completed Projects',
|
||||
'de' => 'Abgeschlossene Projekte',
|
||||
'se' => 'Slutförda projekt',
|
||||
'ar' => 'المشاريع المكتملة',
|
||||
'ru' => 'Завершенные проекты',
|
||||
],
|
||||
'neler-yapariz.stats.satisfied_customers' => [
|
||||
'tr' => 'Mutlu Müşteri',
|
||||
'en' => 'Happy Customer',
|
||||
'de' => 'Zufriedene Kunden',
|
||||
'se' => 'Nöjda kunder',
|
||||
'ar' => 'عميل سعيد',
|
||||
'ru' => 'Довольные клиенты',
|
||||
],
|
||||
'neler-yapariz.stats.expert_employees' => [
|
||||
'tr' => 'Uzman Çalışanlar',
|
||||
'en' => 'Expert Employees',
|
||||
'de' => 'Fachkräfte',
|
||||
'se' => 'Experter',
|
||||
'ar' => 'موظفون خبراء',
|
||||
'ru' => 'Опытные специалисты',
|
||||
],
|
||||
'neler-yapariz.stats.awards_won' => [
|
||||
'tr' => 'Kazanılan Ödüller',
|
||||
'en' => 'Awards Won',
|
||||
'de' => 'Gewonnene Auszeichnungen',
|
||||
'se' => 'Vunna priser',
|
||||
'ar' => 'الجوائز المحققة',
|
||||
'ru' => 'Полученные награды',
|
||||
],
|
||||
'neler-yapariz.stats.completed_projects_number' => [
|
||||
'tr' => '404',
|
||||
'en' => '404',
|
||||
'de' => '404',
|
||||
'se' => '404',
|
||||
'ar' => '404',
|
||||
'ru' => '404',
|
||||
],
|
||||
'neler-yapariz.stats.satisfied_customers_number' => [
|
||||
'tr' => '4615',
|
||||
'en' => '4615',
|
||||
'de' => '4615',
|
||||
'se' => '4615',
|
||||
'ar' => '4615',
|
||||
'ru' => '4615',
|
||||
],
|
||||
'neler-yapariz.stats.expert_employees_number' => [
|
||||
'tr' => '23',
|
||||
'en' => '23',
|
||||
'de' => '23',
|
||||
'se' => '23',
|
||||
'ar' => '23',
|
||||
'ru' => '23',
|
||||
],
|
||||
'neler-yapariz.stats.awards_won_number' => [
|
||||
'tr' => '3',
|
||||
'en' => '3',
|
||||
'de' => '3',
|
||||
'se' => '3',
|
||||
'ar' => '3',
|
||||
'ru' => '3',
|
||||
],
|
||||
'neler-yapariz.what_we_do.highlight' => [
|
||||
'tr' => 'Neler Yaparız?',
|
||||
'en' => 'What We Do?',
|
||||
'de' => 'Was machen wir?',
|
||||
'se' => 'Vad gör vi?',
|
||||
'ar' => 'ماذا نفعل؟',
|
||||
'ru' => 'Что мы делаем?',
|
||||
],
|
||||
'neler-yapariz.what_we_do.title' => [
|
||||
'tr' => 'Neler Yaparız?',
|
||||
'en' => 'What Do We Do?',
|
||||
'de' => 'Was machen wir?',
|
||||
'se' => 'Vad gör vi?',
|
||||
'ar' => 'ماذا نفعل؟',
|
||||
'ru' => 'Что мы делаем?',
|
||||
],
|
||||
'neler-yapariz.what_we_do.description' => [
|
||||
'tr' => 'Sunduğumuz hizmetin tamamı iş ihtiyaçlarınızı ve projelerinizi karşılamak için özel olarak tasarlanmıştır.',
|
||||
'en' => 'The entire service we offer is specifically designed to meet your business needs and projects.',
|
||||
'de' => 'Unser gesamtes Leistungsangebot ist speziell darauf ausgelegt, Ihre geschäftlichen Anforderungen und Projekte abzudecken.',
|
||||
'se' => 'Hela tjänsten vi erbjuder är särskilt utformad för att möta dina affärsbehov och projekt.',
|
||||
'ar' => 'الخدمة التي نقدمها بالكامل مصممة خصيصًا لتلبية احتياجات أعمالكم ومشاريعكم.',
|
||||
'ru' => 'Все наши услуги специально разработаны для удовлетворения потребностей вашего бизнеса и ваших проектов.',
|
||||
],
|
||||
'neler-yapariz.what_we_do.description_end' => [
|
||||
'tr' => '',
|
||||
'en' => '',
|
||||
'de' => '',
|
||||
'se' => '',
|
||||
'ar' => '',
|
||||
'ru' => '',
|
||||
],
|
||||
'neler-yapariz.services.support_24_7.title' => [
|
||||
'tr' => '7/24 Destek',
|
||||
'en' => '24/7 Support',
|
||||
'de' => '24/7-Support',
|
||||
'se' => 'Support dygnet runt',
|
||||
'ar' => 'دعم 24/7',
|
||||
'ru' => 'Поддержка 24/7',
|
||||
],
|
||||
'neler-yapariz.services.support_24_7.description' => [
|
||||
'tr' => 'Hızlı ve etkili çözümlerle her an yanınızdayız.',
|
||||
'en' => 'We are here to help you with expert solutions.',
|
||||
'de' => 'Mit schnellen und effektiven Lösungen sind wir jederzeit an Ihrer Seite.',
|
||||
'se' => 'Vi finns alltid vid din sida med snabba och effektiva lösningar.',
|
||||
'ar' => 'نحن معكم دائمًا بحلول سريعة وفعّالة.',
|
||||
'ru' => 'Мы всегда рядом с вами, предлагая быстрые и эффективные решения.',
|
||||
],
|
||||
'neler-yapariz.services.secure_payments.title' => [
|
||||
'tr' => 'Modern Yazılım',
|
||||
'en' => 'Modern Software',
|
||||
'de' => 'Moderne Software',
|
||||
'se' => 'Modern mjukvara',
|
||||
'ar' => 'برمجيات حديثة',
|
||||
'ru' => 'Современное программное обеспечение',
|
||||
],
|
||||
'neler-yapariz.services.secure_payments.description' => [
|
||||
'tr' => 'En güncel teknolojilerle güvenli altyapılar.',
|
||||
'en' => 'Secure infrastructures with latest tech.',
|
||||
'de' => 'Sichere Infrastrukturen mit modernsten Technologien.',
|
||||
'se' => 'Säkra infrastrukturer med den senaste tekniken.',
|
||||
'ar' => 'بنى تحتية آمنة بأحدث التقنيات.',
|
||||
'ru' => 'Безопасная инфраструктура на основе самых современных технологий.',
|
||||
],
|
||||
'neler-yapariz.services.daily_updates.title' => [
|
||||
'tr' => 'Çapraz Platform',
|
||||
'en' => 'Cross-Platform',
|
||||
'de' => 'Plattformübergreifend',
|
||||
'se' => 'Plattformsoberoende',
|
||||
'ar' => 'متعدد المنصات',
|
||||
'ru' => 'Кроссплатформенно',
|
||||
],
|
||||
'neler-yapariz.services.daily_updates.description' => [
|
||||
'tr' => 'Web ve mobil dünyasında kesintisiz deneyim.',
|
||||
'en' => 'Seamless experience for web and mobile.',
|
||||
'de' => 'Nahtloses Erlebnis in der Web- und Mobile-Welt.',
|
||||
'se' => 'Sömlös upplevelse i webb- och mobilvärlden.',
|
||||
'ar' => 'تجربة سلسة في عالم الويب والموبايل.',
|
||||
'ru' => 'Бесшовный опыт в веб- и мобильной среде.',
|
||||
],
|
||||
'neler-yapariz.services.market_research.title' => [
|
||||
'tr' => 'Kreatif Çözüm',
|
||||
'en' => 'Creative Solution',
|
||||
'de' => 'Kreative Lösung',
|
||||
'se' => 'Kreativ lösning',
|
||||
'ar' => 'حل إبداعي',
|
||||
'ru' => 'Креативное решение',
|
||||
],
|
||||
'neler-yapariz.services.market_research.description' => [
|
||||
'tr' => 'Teknoloji ve sanatı birleştiren projeler.',
|
||||
'en' => 'Projects combining technology and art.',
|
||||
'de' => 'Projekte, die Technologie und Kunst verbinden.',
|
||||
'se' => 'Projekt som förenar teknik och konst.',
|
||||
'ar' => 'مشاريع تجمع بين التكنولوجيا والفن.',
|
||||
'ru' => 'Проекты, объединяющие технологии и искусство.',
|
||||
],
|
||||
'neler-yapariz.how_we_do_it.highlight' => [
|
||||
'tr' => 'Nasıl Yapıyoruz?',
|
||||
'en' => 'How We Do It?',
|
||||
'de' => 'Wie machen wir das?',
|
||||
'se' => 'Hur gör vi det?',
|
||||
'ar' => 'كيف نقوم بذلك؟',
|
||||
'ru' => 'Как мы это делаем?',
|
||||
],
|
||||
'neler-yapariz.how_we_do_it.title' => [
|
||||
'tr' => 'Fikirlerinizi dijital dünyaya taşıyoruz',
|
||||
'en' => 'We bring your ideas into the digital world',
|
||||
'de' => 'Wir bringen Ihre Ideen in die digitale Welt',
|
||||
'se' => 'Vi för dina idéer till den digitala världen',
|
||||
'ar' => 'ننقل أفكاركم إلى العالم الرقمي',
|
||||
'ru' => 'Мы переносим ваши идеи в цифровой мир',
|
||||
],
|
||||
'neler-yapariz.how_we_do_it.description' => [
|
||||
'tr' => 'Fikirlerinizi dijital dünyaya ve profesyonel seslere dönüştürmek için disiplinli bir yol izliyoruz.',
|
||||
'en' => 'We follow a disciplined path to transform your ideas into the digital world and professional sounds.',
|
||||
'de' => 'Wir folgen einem disziplinierten Prozess, um Ihre Ideen in die digitale Welt und in professionelle Audioergebnisse zu überführen.',
|
||||
'se' => 'Vi följer en disciplinerad process för att förvandla dina idéer till digitala lösningar och professionella ljudproduktioner.',
|
||||
'ar' => 'نتبع مسارًا منضبطًا لتحويل أفكاركم إلى العالم الرقمي وإلى مخرجات صوتية احترافية.',
|
||||
'ru' => 'Мы следуем выверенному процессу, чтобы превратить ваши идеи в цифровые решения и профессиональный звук.',
|
||||
],
|
||||
'neler-yapariz.process.step_1.title' => [
|
||||
'tr' => 'Analiz ve Konsept',
|
||||
'en' => 'Analysis & Concept',
|
||||
'de' => 'Analyse & Konzept',
|
||||
'se' => 'Analys och koncept',
|
||||
'ar' => 'التحليل والمفهوم',
|
||||
'ru' => 'Анализ и концепция',
|
||||
],
|
||||
'neler-yapariz.process.step_1.description' => [
|
||||
'tr' => 'İhtiyaçlarınızı belirliyor, hem yazılım hem de ses projeleriniz için stratejik bir yol haritası çiziyoruz.',
|
||||
'en' => 'We identify your needs and draw a strategic roadmap for both your software and audio projects.',
|
||||
'de' => 'Wir ermitteln Ihre Anforderungen und erstellen eine strategische Roadmap für Ihre Software- und Audioprojekte.',
|
||||
'se' => 'Vi identifierar dina behov och tar fram en strategisk färdplan för både mjukvaru- och ljudprojekt.',
|
||||
'ar' => 'نحدّد احتياجاتكم ونضع خارطة طريق استراتيجية لمشاريعكم البرمجية والصوتية.',
|
||||
'ru' => 'Мы определяем ваши потребности и формируем стратегическую дорожную карту для программных и аудиопроектов.',
|
||||
],
|
||||
'neler-yapariz.process.step_2.title' => [
|
||||
'tr' => 'Geliştirme ve Kayıt',
|
||||
'en' => 'Development & Recording',
|
||||
'de' => 'Entwicklung & Aufnahme',
|
||||
'se' => 'Utveckling och inspelning',
|
||||
'ar' => 'التطوير والتسجيل',
|
||||
'ru' => 'Разработка и запись',
|
||||
],
|
||||
'neler-yapariz.process.step_2.description' => [
|
||||
'tr' => 'Kod mimarisini inşa ederken aynı zamanda profesyonel stüdyo ortamında ses prodüksiyonunuzu yapıyoruz.',
|
||||
'en' => 'While building the code architecture, we also produce your audio in a professional studio environment.',
|
||||
'de' => 'Während wir die Code-Architektur aufbauen, produzieren wir gleichzeitig Ihren Sound in einem professionellen Studio.',
|
||||
'se' => 'När vi bygger kodarkitekturen producerar vi samtidigt ditt ljud i en professionell studiomiljö.',
|
||||
'ar' => 'أثناء بناء معمارية الكود، نقوم أيضًا بإنتاج الصوت في بيئة استوديو احترافية.',
|
||||
'ru' => 'Пока мы строим архитектуру кода, мы также создаем ваш аудиопродукт в профессиональной студии.',
|
||||
],
|
||||
'neler-yapariz.process.step_3.title' => [
|
||||
'tr' => 'Test ve Mix-Mastering',
|
||||
'en' => 'Testing & Mix-Mastering',
|
||||
'de' => 'Tests & Mix/Mastering',
|
||||
'se' => 'Test och mix/mastering',
|
||||
'ar' => 'الاختبار والمكس/الماسترينغ',
|
||||
'ru' => 'Тестирование и сведение/мастеринг',
|
||||
],
|
||||
'neler-yapariz.process.step_3.description' => [
|
||||
'tr' => 'Uygulamanızı hatalardan arındırıyor, seslerinizi en yüksek duyum kalitesi için kusursuzca işliyoruz.',
|
||||
'en' => 'We debug your application and process your audio flawlessly for the highest listening quality.',
|
||||
'de' => 'Wir bereinigen Ihre Anwendung von Fehlern und bearbeiten Ihren Sound für höchste Hörqualität.',
|
||||
'se' => 'Vi felsöker din applikation och bearbetar ditt ljud felfritt för högsta ljudkvalitet.',
|
||||
'ar' => 'ننقّي تطبيقكم من الأخطاء ونعالج الصوت بإتقان للوصول إلى أعلى جودة استماع.',
|
||||
'ru' => 'Мы устраняем ошибки в приложении и безупречно обрабатываем звук для максимального качества прослушивания.',
|
||||
],
|
||||
'neler-yapariz.process.step_4.title' => [
|
||||
'tr' => 'Yayına Alım',
|
||||
'en' => 'Final Launch',
|
||||
'de' => 'Go-live',
|
||||
'se' => 'Lansering',
|
||||
'ar' => 'الإطلاق النهائي',
|
||||
'ru' => 'Финальный запуск',
|
||||
],
|
||||
'neler-yapariz.process.step_4.description' => [
|
||||
'tr' => 'Tüm platformlarda stabil çalışan uygulamanızı ve hazır eserlerinizi dijital marketlere sunuyoruz.',
|
||||
'en' => 'We deliver your stable application and ready works to digital marketplaces across all platforms.',
|
||||
'de' => 'Wir veröffentlichen Ihre stabil laufende Anwendung und fertigen Werke auf digitalen Marktplätzen für alle Plattformen.',
|
||||
'se' => 'Vi lanserar din stabila applikation och dina färdiga verk på digitala marknadsplatser för alla plattformar.',
|
||||
'ar' => 'نطلق تطبيقكم المستقر وأعمالكم الجاهزة في الأسواق الرقمية عبر جميع المنصات.',
|
||||
'ru' => 'Мы выводим ваше стабильное приложение и готовые материалы на цифровые площадки всех платформ.',
|
||||
],
|
||||
'neler-yapariz.gallery.image_1_alt' => [
|
||||
'tr' => 'Neler yaparız galeri görseli 1',
|
||||
'en' => 'What we do gallery image 1',
|
||||
'de' => 'Galeriebild 1 - Was wir tun',
|
||||
'se' => 'Bild 1 i galleriet Vad vi gör',
|
||||
'ar' => 'صورة المعرض 1 - ماذا نفعل',
|
||||
'ru' => 'Изображение 1 в галерее Что мы делаем',
|
||||
],
|
||||
'neler-yapariz.gallery.image_2_alt' => [
|
||||
'tr' => 'Neler yaparız galeri görseli 2',
|
||||
'en' => 'What we do gallery image 2',
|
||||
'de' => 'Galeriebild 2 - Was wir tun',
|
||||
'se' => 'Bild 2 i galleriet Vad vi gör',
|
||||
'ar' => 'صورة المعرض 2 - ماذا نفعل',
|
||||
'ru' => 'Изображение 2 в галерее Что мы делаем',
|
||||
],
|
||||
'neler-yapariz.gallery.image_3_alt' => [
|
||||
'tr' => 'Neler yaparız galeri görseli 3',
|
||||
'en' => 'What we do gallery image 3',
|
||||
'de' => 'Galeriebild 3 - Was wir tun',
|
||||
'se' => 'Bild 3 i galleriet Vad vi gör',
|
||||
'ar' => 'صورة المعرض 3 - ماذا نفعل',
|
||||
'ru' => 'Изображение 3 в галерее Что мы делаем',
|
||||
],
|
||||
'neler-yapariz.gallery.image_4_alt' => [
|
||||
'tr' => 'Neler yaparız galeri görseli 4',
|
||||
'en' => 'What we do gallery image 4',
|
||||
'de' => 'Galeriebild 4 - Was wir tun',
|
||||
'se' => 'Bild 4 i galleriet Vad vi gör',
|
||||
'ar' => 'صورة المعرض 4 - ماذا نفعل',
|
||||
'ru' => 'Изображение 4 в галерее Что мы делаем',
|
||||
],
|
||||
'neler-yapariz.image_alt' => [
|
||||
'tr' => 'Neler yaparız illüstrasyonu',
|
||||
'en' => 'What we do illustration',
|
||||
'de' => 'Illustration - Was wir tun',
|
||||
'se' => 'Illustration Vad vi gör',
|
||||
'ar' => 'رسم توضيحي - ماذا نفعل',
|
||||
'ru' => 'Иллюстрация Что мы делаем',
|
||||
],
|
||||
],
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('music_productions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title'); // Main title (usually default language)
|
||||
$table->string('slug')->unique();
|
||||
$table->string('cover_image')->nullable();
|
||||
$table->longText('content')->nullable(); // Main content (usually default language)
|
||||
$table->string('client_name')->nullable(); // Main client name (usually default language)
|
||||
$table->date('production_date')->nullable();
|
||||
$table->json('gallery')->nullable(); // Store gallery images
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->integer('sort_order')->default(0);
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['is_active', 'sort_order']);
|
||||
$table->index('slug');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('music_productions');
|
||||
}
|
||||
};
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('music_productions', function (Blueprint $table) {
|
||||
$table->string('spotify_album_id')->nullable()->unique()->after('slug');
|
||||
$table->string('spotify_url')->nullable()->after('spotify_album_id');
|
||||
$table->string('spotify_cover_url')->nullable()->after('spotify_url');
|
||||
$table->string('spotify_type')->nullable()->after('spotify_cover_url');
|
||||
$table->timestamp('spotify_synced_at')->nullable()->after('spotify_type');
|
||||
|
||||
$table->index('spotify_album_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('music_productions', function (Blueprint $table) {
|
||||
$table->dropIndex(['spotify_album_id']);
|
||||
$table->dropColumn([
|
||||
'spotify_album_id',
|
||||
'spotify_url',
|
||||
'spotify_cover_url',
|
||||
'spotify_type',
|
||||
'spotify_synced_at',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('music_productions', function (Blueprint $table) {
|
||||
$table->string('youtube_video_id')->nullable()->unique()->after('spotify_synced_at');
|
||||
$table->string('youtube_url')->nullable()->after('youtube_video_id');
|
||||
$table->string('youtube_cover_url')->nullable()->after('youtube_url');
|
||||
$table->text('youtube_description')->nullable()->after('youtube_cover_url');
|
||||
$table->timestamp('youtube_synced_at')->nullable()->after('youtube_description');
|
||||
|
||||
$table->index('youtube_video_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('music_productions', function (Blueprint $table) {
|
||||
$table->dropIndex(['youtube_video_id']);
|
||||
$table->dropColumn([
|
||||
'youtube_video_id',
|
||||
'youtube_url',
|
||||
'youtube_cover_url',
|
||||
'youtube_description',
|
||||
'youtube_synced_at',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('company_history_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedSmallInteger('year');
|
||||
$table->string('title');
|
||||
$table->text('content');
|
||||
$table->string('color', 7)->default('#17a2b8');
|
||||
$table->string('icon', 64)->default('heroicon-o-building-office-2');
|
||||
$table->enum('position', ['left', 'right'])->nullable();
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['is_active', 'sort_order']);
|
||||
$table->index('year');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('company_history_items');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('company_history_items', function (Blueprint $table) {
|
||||
$table->string('quarter', 2)->default('Q1')->after('year');
|
||||
$table->index(['year', 'quarter']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('company_history_items', function (Blueprint $table) {
|
||||
$table->dropIndex(['year', 'quarter']);
|
||||
$table->dropColumn('quarter');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('avatar')->nullable()->after('password');
|
||||
$table->string('role')->nullable()->after('avatar');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn(['avatar', 'role']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('proposals', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->uuid('uuid')->unique();
|
||||
$table->string('slug')->unique();
|
||||
$table->string('title');
|
||||
$table->string('client_name');
|
||||
$table->string('client_email')->nullable();
|
||||
$table->longText('content');
|
||||
$table->decimal('total_price', 15, 2)->nullable();
|
||||
$table->string('currency', 10)->default('TRY');
|
||||
$table->string('status', 30)->default('draft'); // draft, sent, accepted, rejected, revised
|
||||
$table->date('valid_until')->nullable();
|
||||
$table->text('client_feedback')->nullable();
|
||||
$table->string('accepted_name')->nullable();
|
||||
$table->timestamp('accepted_at')->nullable();
|
||||
$table->integer('views_count')->default(0);
|
||||
$table->json('meta')->nullable(); // For accent color, logos, etc.
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('proposals');
|
||||
}
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->string('username')->nullable()->unique()->after('status');
|
||||
$table->string('password')->nullable()->after('username');
|
||||
$table->string('signed_internship_form_path')->nullable()->after('password');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn(['username', 'password', 'signed_internship_form_path']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('awards', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title')->nullable(); // Original/fallback title
|
||||
$table->text('description')->nullable(); // Original/fallback description
|
||||
$table->string('issuer')->nullable(); // Original/fallback issuer
|
||||
$table->date('award_date')->nullable();
|
||||
$table->string('image')->nullable();
|
||||
$table->string('external_link')->nullable();
|
||||
$table->boolean('is_featured')->default(false);
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->string('category', 64)->default('general');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['is_active', 'sort_order']);
|
||||
$table->index('is_featured');
|
||||
$table->index('award_date');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('awards');
|
||||
}
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->string('to_be_signed_internship_form_path')->nullable()->after('signed_internship_form_path');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn('to_be_signed_internship_form_path');
|
||||
});
|
||||
}
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->date('internship_start_date')->nullable()->after('to_be_signed_internship_form_path');
|
||||
$table->date('internship_end_date')->nullable()->after('internship_start_date');
|
||||
$table->integer('internship_total_days')->nullable()->after('internship_end_date');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn(['internship_start_date', 'internship_end_date', 'internship_total_days']);
|
||||
});
|
||||
}
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->string('id_photocopy_path')->nullable()->after('contract_path');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn('id_photocopy_path');
|
||||
});
|
||||
}
|
||||
};
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->string('github_repo')->nullable()->after('internship_total_days');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('career_applications', function (Blueprint $table) {
|
||||
$table->dropColumn('github_repo');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Page;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class AppDevelopmentPageSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected array $englishTranslations = [
|
||||
'Google Play ve App Store\'da' => 'On Google Play and the App Store, we build',
|
||||
'başarılı uygulamalar' => 'successful apps',
|
||||
'geliştiriyoruz.' => 'for you.',
|
||||
'Trunçgil Teknoloji olarak Android ve iOS uygulamalarını fikirden mağaza yayınına kadar titizlikle hayata geçiriyoruz.' => 'At Trunçgil Teknoloji, we meticulously bring Android and iOS apps to life—from idea to store launch.',
|
||||
'Projenizi Konuşalım' => 'Let\'s Talk About Your Project',
|
||||
'Trunçgil Teknoloji mobil uygulama geliştirme' => 'Trunçgil Teknoloji mobile app development',
|
||||
'Geliştirme Yetkinliklerimiz' => 'Our Development Capabilities',
|
||||
'Trunçgil Teknoloji, uygulamanızı mağazaya hazır hale getirmek için' => 'Trunçgil Teknoloji considers',
|
||||
'her detayı' => 'every detail',
|
||||
'düşünür.' => 'to get your app store-ready.',
|
||||
'Keşif ve Strateji' => 'Discovery & Strategy',
|
||||
'UI/UX Tasarım' => 'UI/UX Design',
|
||||
'Native Android Geliştirme' => 'Native Android Development',
|
||||
'iOS Geliştirme' => 'iOS Development',
|
||||
'Güvenlik ve Test' => 'Security & Testing',
|
||||
'Google Play Uyumluluğu' => 'Google Play Compliance',
|
||||
'App Store İnceleme Hazırlığı' => 'App Store Review Readiness',
|
||||
'Yayın Sonrası Destek' => 'Post-Launch Support',
|
||||
'Geliştirme Sürecimiz' => 'Our Development Process',
|
||||
'Trunçgil Teknoloji ile fikirden mağazaya' => 'With Trunçgil Teknoloji, from idea to store in',
|
||||
'dört adımda' => 'four steps',
|
||||
'ilerleyin.' => '— here\'s how.',
|
||||
'Trunçgil Teknoloji uygulama geliştirme süreci' => 'Trunçgil Teknoloji app development process',
|
||||
'Keşif ve Analiz' => 'Discovery & Analysis',
|
||||
'Trunçgil Teknoloji ekibi, hedef kitlenizi, iş hedeflerinizi ve teknik gereksinimlerinizi derinlemesine analiz ederek projenin yol haritasını birlikte oluşturur.' => 'The Trunçgil Teknoloji team deeply analyzes your audience, business goals, and technical requirements to co-create the project roadmap.',
|
||||
'Tasarım ve Prototip' => 'Design & Prototype',
|
||||
'Kullanıcı deneyimi öncelikli arayüz tasarımları ve tıklanabilir prototiplerle Google Play ve App Store standartlarına uygun bir deneyim planlanır.' => 'UX-first interface designs and clickable prototypes are planned to meet Google Play and App Store standards.',
|
||||
'Geliştirme ve Test' => 'Development & Testing',
|
||||
'Android ve iOS platformlarında titiz kodlama, otomatik testler ve cihaz uyumluluğu kontrolleriyle uygulamanız hatasız şekilde inşa edilir.' => 'Your app is built flawlessly with meticulous coding, automated tests, and device compatibility checks on Android and iOS.',
|
||||
'Mağaza Yayını ve Destek' => 'Store Launch & Support',
|
||||
'Google Play Console ve App Store Connect süreçlerini Trunçgil Teknoloji yönetir; yayın sonrası izleme, güncelleme ve bakım desteği sunar.' => 'Trunçgil Teknoloji manages Google Play Console and App Store Connect processes, plus post-launch monitoring, updates, and maintenance.',
|
||||
'Sık Sorulan Sorular' => 'Frequently Asked Questions',
|
||||
'Uygulama geliştirme hakkında' => 'About app development —',
|
||||
'merak ettikleriniz' => 'your questions answered',
|
||||
'Trunçgil Teknoloji hangi platformlarda uygulama geliştiriyor?' => 'Which platforms does Trunçgil Teknoloji develop apps for?',
|
||||
'Trunçgil Teknoloji olarak native Android (Kotlin), native iOS (Swift) ve cross-platform (Flutter) çözümler sunuyoruz. Uygulamalarınız Google Play Store ve Apple App Store\'da yayınlanmaya hazır şekilde teslim edilir.' => 'At Trunçgil Teknoloji, we offer native Android (Kotlin), native iOS (Swift), and cross-platform (Flutter) solutions. Your apps are delivered ready for Google Play Store and Apple App Store.',
|
||||
'Google Play\'e yükleme sürecini siz mi yönetiyorsunuz?' => 'Do you manage the Google Play upload process?',
|
||||
'Evet. Trunçgil Teknoloji, Google Play Console hesap kurulumu, uygulama imzalama, store listing hazırlığı, ekran görüntüleri ve politika uyumluluk kontrollerini sizin adınıza titizlikle yürütür.' => 'Yes. Trunçgil Teknoloji meticulously handles Google Play Console setup, app signing, store listing, screenshots, and policy compliance on your behalf.',
|
||||
'App Store inceleme sürecinde red almamak için ne yapıyorsunuz?' => 'How do you avoid App Store rejections?',
|
||||
'Apple\'ın Human Interface Guidelines, gizlilik politikası ve uygulama içi satın alma kurallarına uygunluk denetimi yapıyoruz. Metadata, açıklamalar ve gizlilik etiketleri yayın öncesinde Trunçgil Teknoloji tarafından kontrol edilir.' => 'We audit compliance with Apple\'s Human Interface Guidelines, privacy policy, and in-app purchase rules. Metadata, descriptions, and privacy labels are checked by Trunçgil Teknoloji before submission.',
|
||||
'Bir uygulama projesi ne kadar sürer?' => 'How long does an app project take?',
|
||||
'Proje kapsamına göre değişmekle birlikte, orta ölçekli bir uygulama Trunçgil Teknoloji sürecinde genellikle 8–16 hafta arasında tamamlanır. Keşif aşamasında size net bir zaman çizelgesi sunulur.' => 'Depending on scope, a mid-size app typically takes 8–16 weeks in the Trunçgil Teknoloji process. A clear timeline is provided during discovery.',
|
||||
'Mevcut uygulamamı yeniden yazabilir veya güncelleyebilir misiniz?' => 'Can you rewrite or update my existing app?',
|
||||
'Kesinlikle. Trunçgil Teknoloji, legacy kod modernizasyonu, performans iyileştirmesi, yeni OS sürümlerine uyum ve mağaza güncellemeleri konularında kapsamlı destek sağlar.' => 'Absolutely. Trunçgil Teknoloji provides comprehensive support for legacy modernization, performance improvements, new OS compatibility, and store updates.',
|
||||
'Yayın sonrası bakım hizmeti veriyor musunuz?' => 'Do you offer post-launch maintenance?',
|
||||
'Evet. Trunçgil Teknoloji, yayın sonrası hata takibi, kullanıcı geri bildirimlerine yanıt, güvenlik yamaları ve periyodik özellik güncellemeleri için esnek bakım paketleri sunar.' => 'Yes. Trunçgil Teknoloji offers flexible maintenance packages for post-launch bug tracking, user feedback, security patches, and periodic feature updates.',
|
||||
'Neden Trunçgil Teknoloji?' => 'Why Trunçgil Teknoloji?',
|
||||
'Google Play ve App Store\'da başarılı olmanız için' => 'Six strong reasons to succeed on',
|
||||
'6 güçlü neden' => 'Google Play & App Store',
|
||||
'Trunçgil Teknoloji mobil uygulamalar' => 'Trunçgil Teknoloji mobile apps',
|
||||
'Android ve iOS Uzmanlığı' => 'Android & iOS Expertise',
|
||||
'Trunçgil Teknoloji, Kotlin, Swift ve Flutter teknolojilerinde deneyimli ekibiyle her iki platformda da performanslı uygulamalar geliştirir.' => 'Trunçgil Teknoloji builds high-performance apps on both platforms with an experienced team in Kotlin, Swift, and Flutter.',
|
||||
'Google Play Politika Uyumu' => 'Google Play Policy Compliance',
|
||||
'Veri güvenliği, izin yönetimi ve içerik politikalarına tam uyum sağlayarak uygulamanızın mağazada sorunsuz yayınlanmasını garanti altına alırız.' => 'We ensure full compliance with data security, permissions, and content policies for smooth store publication.',
|
||||
'App Store İnceleme Deneyimi' => 'App Store Review Experience',
|
||||
'Human Interface Guidelines, gizlilik etiketleri ve metadata gereksinimlerini önceden ele alarak red riskini minimuma indiriyoruz.' => 'We minimize rejection risk by addressing Human Interface Guidelines, privacy labels, and metadata requirements upfront.',
|
||||
'Kapsamlı Test Süreci' => 'Comprehensive Testing Process',
|
||||
'Birim testleri, entegrasyon testleri ve gerçek cihazlarda manuel QA ile uygulamanızı yayına hazır hale getiriyoruz.' => 'Unit tests, integration tests, and manual QA on real devices prepare your app for launch.',
|
||||
'Performans Optimizasyonu' => 'Performance Optimization',
|
||||
'Pil tüketimi, açılış süresi ve bellek kullanımı Trunçgil Teknoloji tarafından sürekli ölçülür ve iyileştirilir.' => 'Battery usage, launch time, and memory consumption are continuously measured and improved by Trunçgil Teknoloji.',
|
||||
'Sürekli Bakım ve Destek' => 'Ongoing Maintenance & Support',
|
||||
'Yayın sonrası hata düzeltmeleri, OS güncellemelerine uyum ve yeni özellik geliştirmeleri için yanınızdayız.' => 'We\'re with you for post-launch bug fixes, OS update compatibility, and new feature development.',
|
||||
'Mutlu Müşteriler' => 'Happy Clients',
|
||||
'Trunçgil Teknoloji ile çalışan müşterilerimizin' => 'What our clients say about working with',
|
||||
'deneyimleri' => 'Trunçgil Teknoloji',
|
||||
'Trunçgil Teknoloji, e-ticaret uygulamamızı Google Play\'e sorunsuz taşıdı. İnceleme sürecinde hiç red almadık; her adımda bizi bilgilendirdiler.' => 'Trunçgil Teknoloji smoothly brought our e-commerce app to Google Play. We got zero rejections—they kept us informed at every step.',
|
||||
'Kurucu, Perakende Startup' => 'Founder, Retail Startup',
|
||||
'iOS uygulamamız App Store\'da ilk denemede onaylandı. Trunçgil Teknoloji\'nin titiz test süreci ve tasarım kalitesi gerçekten fark yarattı.' => 'Our iOS app was approved on the first App Store submission. Trunçgil Teknoloji\'s rigorous testing and design quality made the difference.',
|
||||
'Ürün Müdürü, SaaS Şirketi' => 'Product Manager, SaaS Company',
|
||||
'Hem Android hem iOS sürümünü aynı anda teslim ettiler. Trunçgil Teknoloji ekibi proje boyunca şeffaf iletişim kurdu ve söz verilen tarihte yayınladık.' => 'They delivered both Android and iOS versions simultaneously. The Trunçgil Teknoloji team communicated transparently and we launched on schedule.',
|
||||
'CTO, Fintech Girişimi' => 'CTO, Fintech Startup',
|
||||
'Yayın sonrası destek paketleri sayesinde uygulamamız her yeni Android sürümüne sorunsuz uyum sağlıyor. Trunçgil Teknoloji\'ye güveniyoruz.' => 'Thanks to post-launch support, our app adapts smoothly to every new Android version. We trust Trunçgil Teknoloji.',
|
||||
'Operasyon Direktörü' => 'Operations Director',
|
||||
'Kurumsal mobil uygulamamızın güvenlik gereksinimlerini Trunçgil Teknoloji mükemmel karşıladı. Google Play ve kurumsal MDM dağıtımı sorunsuz tamamlandı.' => 'Trunçgil Teknoloji perfectly met our enterprise app security requirements. Google Play and MDM deployment completed flawlessly.',
|
||||
'IT Müdürü, Holding' => 'IT Manager, Holding Company',
|
||||
'Prototipten mağaza yayınına kadar tüm süreçte Trunçgil Teknoloji yanımızdaydı. Kullanıcılarımız uygulamanın akıcılığından çok memnun.' => 'Trunçgil Teknoloji was with us from prototype to store launch. Our users love how smooth the app feels.',
|
||||
'Pazarlama Müdürü' => 'Marketing Manager',
|
||||
'Uygulamanızı Google Play ve App Store\'da' => 'Let\'s launch your app on',
|
||||
'hayata geçirelim.' => 'Google Play & App Store.',
|
||||
'Trunçgil Teknoloji ekibi, projenizi dinlemeye ve size özel bir yol haritası sunmaya hazır.' => 'The Trunçgil Teknoloji team is ready to hear your project and offer a tailored roadmap.',
|
||||
'Ücretsiz Danışmanlık Alın' => 'Get a Free Consultation',
|
||||
'Trunçgil Teknoloji uygulama geliştirme' => 'Trunçgil Teknoloji app development',
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$author = User::query()->first();
|
||||
if (!$author) {
|
||||
$this->command?->warn('Kullanıcı bulunamadı, seeder atlandı.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->englishTranslations as $key => $english) {
|
||||
// Translations are managed by SiteTranslationsSeeder (app_development_translations.php).
|
||||
}
|
||||
|
||||
Page::updateOrCreate(
|
||||
['slug' => 'uygulama-gelistirme'],
|
||||
[
|
||||
'author_id' => $author->id,
|
||||
'title' => 'Uygulama Geliştirme',
|
||||
'excerpt' => 'Trunçgil Teknoloji olarak Android ve iOS uygulamalarını Google Play ve App Store\'da yayınlanmaya hazır şekilde titizlikle geliştiriyoruz.',
|
||||
'content' => '',
|
||||
'template' => 'services.app-development',
|
||||
'featured_image' => null,
|
||||
'custom_header_blade' => 'partials.header-app-development',
|
||||
'status' => 'published',
|
||||
'is_homepage' => false,
|
||||
'show_in_menu' => false,
|
||||
'sort_order' => 15,
|
||||
'published_at' => now(),
|
||||
'meta_title' => 'Uygulama Geliştirme | Trunçgil Teknoloji',
|
||||
'meta_description' => 'Trunçgil Teknoloji ile Google Play ve App Store için native Android, iOS ve Flutter uygulama geliştirme. Keşiften mağaza yayınına titiz süreç.',
|
||||
]
|
||||
);
|
||||
|
||||
$this->command?->info('✅ Uygulama Geliştirme sayfası ve çevirileri oluşturuldu.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Award;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class AwardSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$awards = [
|
||||
[
|
||||
'title' => 'Here & AWS & THY Hackathon Birinciliği',
|
||||
'description' => '21 Mart 2019 yılında Here, Amazon Web Services (AWS) ve Türk Hava Yolları\'nın (THY) ortaklaşa yürüttüğü Hackathon kapsamında büyük bir başarı göstererek 1. lik ödülünü almaya hak kazandık.',
|
||||
'issuer' => 'Here & AWS & THY',
|
||||
'award_date' => '2019-03-21',
|
||||
'image' => 'assets/img/awards/truncgil-hackathon-birincisi-here-aws-thy.png',
|
||||
'external_link' => 'https://truncgil.com',
|
||||
'is_featured' => true,
|
||||
'is_active' => true,
|
||||
'sort_order' => 1,
|
||||
'category' => 'hackathon',
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Here & AWS & THY Hackathon First Place',
|
||||
'description' => 'On March 21, 2019, we achieved outstanding success and won the 1st place award in the Hackathon jointly organized by Here, Amazon Web Services (AWS), and Turkish Airlines (THY).',
|
||||
'issuer' => 'Here, AWS & Turkish Airlines',
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'Gaziantep Teknopark Yılın İhracat Ödülü',
|
||||
'description' => 'İhracat faaliyetlerindeki üstün başarımızdan ötürü 26 Aralık 2019 yılında Gaziantep Teknopark tarafından "Yılın İhracat Ödülü"ne layık görüldük.',
|
||||
'issuer' => 'Gaziantep Teknopark',
|
||||
'award_date' => '2019-12-26',
|
||||
'image' => 'assets/img/awards/truncgil-teknoloji-yilin-ihracat-odulu.png',
|
||||
'external_link' => 'https://truncgil.com',
|
||||
'is_featured' => true,
|
||||
'is_active' => true,
|
||||
'sort_order' => 2,
|
||||
'category' => 'export',
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Gaziantep Technopark Export of the Year Award',
|
||||
'description' => 'Due to our outstanding achievements in export activities, we were deemed worthy of the "Export of the Year Award" by Gaziantep Technopark on December 26, 2019.',
|
||||
'issuer' => 'Gaziantep Technopark',
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'title' => 'HackSmarCity Hackathon Birinciliği',
|
||||
'description' => 'Gaziantep Teknopark, Target Teknoloji Transfer Ofisi, Gaziantep Büyükşehir Belediyesi ve Gaziantep Bilişim\'in ortaklaşa düzenlediği akıllı şehir çözümleri temalı HackSmarCity Hackathon\'unda 1. lik elde ettik.',
|
||||
'issuer' => 'Gaziantep Büyükşehir Belediyesi & Teknopark',
|
||||
'award_date' => '2019-11-15', // Approximate date in 2019/2020
|
||||
'image' => 'assets/img/awards/truncgil-teknoloji-hacksmarcity.jpg',
|
||||
'external_link' => 'https://truncgil.com',
|
||||
'is_featured' => false,
|
||||
'is_active' => true,
|
||||
'sort_order' => 3,
|
||||
'category' => 'hackathon',
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'HackSmarCity Hackathon First Place',
|
||||
'description' => 'We achieved 1st place in the HackSmarCity Hackathon themed around smart city solutions, jointly organized by Gaziantep Technopark, Target Technology Transfer Office, Gaziantep Metropolitan Municipality, and Gaziantep Bilisim.',
|
||||
'issuer' => 'Gaziantep Metropolitan Municipality & Technopark',
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($awards as $awardData) {
|
||||
$translations = $awardData['translations'] ?? [];
|
||||
unset($awardData['translations']);
|
||||
|
||||
$award = Award::updateOrCreate(
|
||||
[
|
||||
'award_date' => $awardData['award_date'],
|
||||
'image' => $awardData['image'],
|
||||
],
|
||||
$awardData
|
||||
);
|
||||
|
||||
// Save default (Turkish) translations in translation table too so HasTranslations resolves correctly
|
||||
$award->setTranslation('title', 'tr', $awardData['title'], 'published');
|
||||
$award->setTranslation('description', 'tr', $awardData['description'], 'published');
|
||||
$award->setTranslation('issuer', 'tr', $awardData['issuer'], 'published');
|
||||
|
||||
// Save English translations
|
||||
foreach ($translations as $langCode => $fields) {
|
||||
foreach ($fields as $fieldName => $value) {
|
||||
$award->setTranslation($fieldName, $langCode, $value, 'published');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\CompanyHistoryItem;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class CompanyHistoryItemSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$items = [
|
||||
[
|
||||
'year' => 2014,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Kuruluş',
|
||||
'content' => 'Trunçgil Teknoloji, 2014 yılında Ümit Tunç tarafından Gaziantep\'te kuruldu.',
|
||||
'color' => '#17a2b8',
|
||||
'icon' => 'heroicon-o-building-office-2',
|
||||
'sort_order' => 1,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Foundation',
|
||||
'content' => 'Trunçgil Technology was founded in Gaziantep in 2014 by Ümit Tunç.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Основание',
|
||||
'content' => 'Компания Trunçgil Technology была основана в 2014 году Умитом Тунчем в Газiantепе.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Gründung',
|
||||
'content' => 'Trunçgil Technology wurde 2014 von Ümit Tunç in Gaziantep gegründet.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2015,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Akademik Altyapı',
|
||||
'content' => 'E-SER Akademik Yayın Evi ile çözüm ortaklığı kurarak indeksli dergiler için akademik yayın altyapısı geliştirdi.',
|
||||
'color' => '#f5a623',
|
||||
'icon' => 'heroicon-o-academic-cap',
|
||||
'sort_order' => 2,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Academic Infrastructure',
|
||||
'content' => 'Established a solution partnership with E-SER Academic Publishing to develop publishing infrastructure for indexed journals.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Академическая инфраструктура',
|
||||
'content' => 'В партнёрстве с E-SER Academic Publishing была создана инфраструктура для академических публикаций индексируемых журналов.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Akademische Infrastruktur',
|
||||
'content' => 'In Partnerschaft mit E-SER Academic Publishing wurde die Infrastruktur für indexierte Fachzeitschriften aufgebaut.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2016,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'İlk İhracat',
|
||||
'content' => 'İlk ihracatini gerçekleştirerek e-ticaret alanında Amerika Birleşik Devletleri\'nin New Jersey eyaletinde faaliyet göstermeye başladı.',
|
||||
'color' => '#e91e63',
|
||||
'icon' => 'heroicon-o-globe-alt',
|
||||
'sort_order' => 3,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'First Export',
|
||||
'content' => 'Completed its first export and began operations in e-commerce in New Jersey, United States.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Первый экспорт',
|
||||
'content' => 'Компания осуществила первый экспорт и начала деятельность в сфере электронной коммерции в штате Нью-Джерси, США.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Erster Export',
|
||||
'content' => 'Mit dem ersten Export wurden E-Commerce-Aktivitäten im US-Bundesstaat New Jersey aufgenommen.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2017,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'TRDoktor.com',
|
||||
'content' => 'TRDoktor.com iş birliğiyle Türkiye genelinde aylık bir milyondan fazla kullanıcıya hizmet veren dijital sağlık altyapısına katkı sağladı.',
|
||||
'color' => '#2c3e50',
|
||||
'icon' => 'heroicon-o-users',
|
||||
'sort_order' => 4,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'TRDoktor.com',
|
||||
'content' => 'Through its partnership with TRDoktor.com, contributed to a digital health platform serving more than one million monthly users across Turkey.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'TRDoktor.com',
|
||||
'content' => 'В рамках партнёрства с TRDoktor.com компания внесла вклад в создание цифровой платформы здравоохранения, обслуживающей более миллиона пользователей ежемесячно по всей Турции.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'TRDoktor.com',
|
||||
'content' => 'Durch die Partnerschaft mit TRDoktor.com wurde eine digitale Gesundheitsplattform mit über einer Million monatlicher Nutzer in der Türkei unterstützt.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2018,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Eğitim Teknolojileri',
|
||||
'content' => 'Amerikan Kültür Koleji\'nin eğitim altyapısında kullanılmak üzere kurumsal eğitim uygulamaları geliştirdi.',
|
||||
'color' => '#5dade2',
|
||||
'icon' => 'heroicon-o-academic-cap',
|
||||
'sort_order' => 5,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Education Technology',
|
||||
'content' => 'Developed corporate education applications for the learning infrastructure of American Culture College.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Образовательные технологии',
|
||||
'content' => 'Были разработаны корпоративные образовательные приложения для образовательной инфраструктуры American Culture College.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Bildungstechnologie',
|
||||
'content' => 'Für die Bildungsinfrastruktur des American Culture College wurden unternehmerische Lernanwendungen entwickelt.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2019,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Gaziantep Teknopark',
|
||||
'content' => 'Gaziantep Teknopark bünyesinde Ar-Ge ve yazılım geliştirme faaliyetlerini sürdürecek şekilde şube açtı.',
|
||||
'color' => '#27ae60',
|
||||
'icon' => 'heroicon-o-building-office-2',
|
||||
'sort_order' => 6,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Gaziantep Technopark',
|
||||
'content' => 'Opened a branch at Gaziantep Technopark to continue R&D and software development operations.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Технопарк Gaziantep',
|
||||
'content' => 'Открыт филиал в технопарке Gaziantep для продолжения деятельности в области НИОКР и разработки программного обеспечения.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Niederlassung Gaziantep Technopark',
|
||||
'content' => 'Eröffnung einer Niederlassung im Gaziantep Technopark zur Fortführung von F&E- und Softwareentwicklungsaktivitäten.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2019,
|
||||
'quarter' => 'Q2',
|
||||
'title' => 'HERE Maps Hackathon',
|
||||
'content' => 'Almanya menşeli HERE Maps\'in Türkiye genelinde düzenlediği hackathon\'da birincilik ödülünü kazandı; konum teknolojileri alanındaki yetkinliğimizi ulusal düzeyde tescilledik.',
|
||||
'color' => '#1abc9c',
|
||||
'icon' => 'heroicon-o-trophy',
|
||||
'sort_order' => 7,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'HERE Maps Hackathon',
|
||||
'content' => 'Won first place in the nationwide hackathon organized in Turkey by Germany-based HERE Maps, affirming our expertise in location technologies.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'HERE Maps Hackathon',
|
||||
'content' => 'Заняли первое место во всетурецком хакатоне, организованном компанией HERE Maps (Германия), подтвердив нашу компетенцию в области геолокационных технологий.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'HERE Maps Hackathon – 1. Platz',
|
||||
'content' => 'Erster Platz beim landesweiten Hackathon von HERE Maps (Deutschland) in der Türkei — ein Beleg unserer Kompetenz in Geolokationstechnologien.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2020,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Otto Planner 2020',
|
||||
'content' => 'Almanya\'nın Bochum kentinde faaliyet gösteren P-D Refractories GmbH için Otto Planner 2020 uygulamasını geliştirdi ve şirketin dijital dönüşüm sürecini başlattı.',
|
||||
'color' => '#8e44ad',
|
||||
'icon' => 'heroicon-o-light-bulb',
|
||||
'sort_order' => 8,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Otto Planner 2020',
|
||||
'content' => 'Developed the Otto Planner 2020 application for P-D Refractories GmbH in Bochum, Germany, and initiated the company\'s digital transformation.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Otto Planner 2020',
|
||||
'content' => 'Разработано приложение Otto Planner 2020 для P-D Refractories GmbH в Бохуме (Германия) и инициирован процесс цифровой трансформации компании.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Otto Planner 2020',
|
||||
'content' => 'Entwicklung der Anwendung Otto Planner 2020 für P-D Refractories GmbH in Bochum und Einleitung der digitalen Transformation des Unternehmens.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2021,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'IoT Dashboard',
|
||||
'content' => 'Olimpiyat Otomasyon iş birliğiyle, endüstriyel PLC ve benzeri cihazların uzaktan yönetimi ve kontrolünü sağlayan; RS232 ve WebSocket standartları üzerinden haberleşen IoT Dashboard uygulaması geliştirildi. Nesnelerin İnterneti (IoT) kapsamındaki bu çözüm, Trunçgil Smart altyapısı üzerinde hazırlanmıştır.',
|
||||
'color' => '#6c5ce7',
|
||||
'icon' => 'heroicon-o-light-bulb',
|
||||
'sort_order' => 9,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'IoT Dashboard',
|
||||
'content' => 'In partnership with Olimpiyat Otomasyon, an IoT Dashboard application was developed for remote management and control of industrial PLCs and related devices, communicating via RS232 and WebSocket standards. This Internet of Things (IoT) solution was built on the Trunçgil Smart platform.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'IoT Dashboard',
|
||||
'content' => 'В партнёрстве с Olimpiyat Otomasyon разработано приложение IoT Dashboard для удалённого управления и контроля промышленных ПЛК и аналогичных устройств с коммуникацией через стандарты RS232 и WebSocket. Это решение в области Интернета вещей (IoT) создано на платформе Trunçgil Smart.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'IoT Dashboard',
|
||||
'content' => 'In Partnerschaft mit Olimpiyat Otomasyon wurde eine IoT-Dashboard-Anwendung zur Fernverwaltung und -steuerung industrieller SPS und vergleichbarer Geräte entwickelt — mit Kommunikation über RS232- und WebSocket-Standards. Die IoT-Lösung basiert auf der Trunçgil Smart-Infrastruktur.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2021,
|
||||
'quarter' => 'Q2',
|
||||
'title' => 'PDLC Smart Glass',
|
||||
'content' => 'Hollanda\'da faaliyet gösteren HaHa Group BV için PDLC Smart Glass ürünlerine yönelik e-ticaret altyapısı ve dijital satış kanalları geliştirildi.',
|
||||
'color' => '#2980b9',
|
||||
'icon' => 'heroicon-o-globe-alt',
|
||||
'sort_order' => 10,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'PDLC Smart Glass',
|
||||
'content' => 'Built e-commerce infrastructure and digital sales channels for PDLC Smart Glass products for HaHa Group BV, based in the Netherlands.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'PDLC Smart Glass',
|
||||
'content' => 'Для HaHa Group BV (Нидерланды) создана инфраструктура электронной коммерции и цифровые каналы продаж для продуктов PDLC Smart Glass.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'PDLC Smart Glass',
|
||||
'content' => 'Aufbau der E-Commerce-Infrastruktur und digitaler Vertriebskanäle für PDLC Smart Glass-Produkte der HaHa Group BV (Niederlande).',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2022,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Dijimind Akademi',
|
||||
'content' => 'Dijimind Akademi ile Bloom taksonomisini merkeze alarak LGS ve YKS öğrencilerinin sınava hazırlık yapay zeka koçluğunu yapan sistem geliştirilmiştir. Çalışmalar günümüzde de hızlanarak devam etmektedir ve Türkiye genelinde 100\'den fazla kuruma ve 30.000\'den fazla öğrenciye hizmet vermektedir.',
|
||||
'color' => '#16a085',
|
||||
'icon' => 'heroicon-o-academic-cap',
|
||||
'sort_order' => 11,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Dijimind Academy',
|
||||
'content' => 'Together with Dijimind Academy, a system was developed to provide AI-powered exam preparation coaching for LGS and YKS students, centered on Bloom\'s taxonomy. Development continues to accelerate to this day, serving more than 100 institutions and over 30,000 students across Turkey.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Академия Dijimind',
|
||||
'content' => 'Совместно с Dijimind Academy разработана система ИИ-коучинга для подготовки к экзаменам LGS и YKS на основе таксономии Блума. Разработка продолжается и по сей день — более 100 учреждений и свыше 30 000 студентов по всей Турции.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Dijimind Akademie',
|
||||
'content' => 'Gemeinsam mit Dijimind Akademi wurde ein System entwickelt, das LGS- und YKS-Schülern eine KI-gestützte Prüfungsvorbereitung bietet — mit Blooms Taxonomie im Mittelpunkt. Die Entwicklung schreitet bis heute beschleunigt voran und bedient über 100 Institutionen und mehr als 30.000 Schüler in der gesamten Türkei.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2022,
|
||||
'quarter' => 'Q3',
|
||||
'title' => 'Rhapsode Academic',
|
||||
'content' => '2022 yılının 3. çeyreğinde İngiltere merkezli Rhapsode Academic yayın evi ile manuscript sistem altyapıları üzerinde çalışmalara başlanmıştır. Scopus, Crossref, DOI, Open Access, iThenticate ve Creative Commons entegrasyonları bu altyapının temel bileşenlerini oluşturmaktadır. Çalışmalar günümüzde de güncellenerek devam etmektedir.',
|
||||
'color' => '#9b59b6',
|
||||
'icon' => 'heroicon-o-academic-cap',
|
||||
'sort_order' => 12,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Rhapsode Academic',
|
||||
'content' => 'In the third quarter of 2022, work began with UK-based academic publisher Rhapsode Academic on manuscript system infrastructure. Integrations with Scopus, Crossref, DOI, Open Access, iThenticate, and Creative Commons form the core components of this platform. Development continues to this day with ongoing updates.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Rhapsode Academic',
|
||||
'content' => 'В третьем квартале 2022 года началась работа с британским академическим издательством Rhapsode Academic над инфраструктурой системы рукописей. Интеграции со Scopus, Crossref, DOI, Open Access, iThenticate и Creative Commons составляют ключевые компоненты платформы. Разработка продолжается с регулярными обновлениями.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Rhapsode Academic',
|
||||
'content' => 'Im dritten Quartal 2022 begannen die Arbeiten mit dem in Großbritannien ansässigen akademischen Verlag Rhapsode Academic an der Manuskript-Systeminfrastruktur. Integrationen mit Scopus, Crossref, DOI, Open Access, iThenticate und Creative Commons bilden die Kernkomponenten dieser Plattform. Die Entwicklung wird bis heute mit fortlaufenden Aktualisierungen fortgesetzt.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2023,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Stellar Construction',
|
||||
'content' => 'Rusya\'nın Moskova merkezli, endüstriyel inşaat alanında faaliyet gösteren Ant Yapı ortaklığına sahip Stellar Construction için QA, QC ve QMS (kalite yönetim sistemi) uygulaması geliştirildi. Proje, Rusya\'nın çeşitli şantiye sahalarında başarılı bir şekilde kullanılmaktadır.',
|
||||
'color' => '#c0392b',
|
||||
'icon' => 'heroicon-o-building-office-2',
|
||||
'sort_order' => 13,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Stellar Construction',
|
||||
'content' => 'A QA, QC, and QMS (quality management system) application was developed for Stellar Construction, a Moscow-based industrial construction company and partner of Ant Yapı. The project is successfully deployed across multiple construction sites throughout Russia.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Stellar Construction',
|
||||
'content' => 'Для Stellar Construction — московской компании промышленного строительства, партнёра Ant Yapı — разработано приложение QA, QC и QMS (система управления качеством). Проект успешно используется на различных строительных площадках по всей России.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Stellar Construction',
|
||||
'content' => 'Für Stellar Construction, ein in Moskau ansässiges Industriebauunternehmen und Partner von Ant Yapı, wurde eine QA-, QC- und QMS-Anwendung (Qualitätsmanagementsystem) entwickelt. Das Projekt wird erfolgreich auf zahlreichen Baustellen in Russland eingesetzt.',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'year' => 2024,
|
||||
'quarter' => 'Q1',
|
||||
'title' => 'Trunçgil Parabol Muhasebe',
|
||||
'content' => '2024 yılında İstanbul menşeli Uzman Şirketim ile White Label lisans anlaşması yapılarak Trunçgil Parabol Muhasebe uygulaması ve NES E-Fatura sistemleri üzerinde çalışmalar tamamlanmış ve yayına alınmıştır.',
|
||||
'color' => '#3498db',
|
||||
'icon' => 'heroicon-o-light-bulb',
|
||||
'sort_order' => 14,
|
||||
'translations' => [
|
||||
'en' => [
|
||||
'title' => 'Trunçgil Parabol Accounting',
|
||||
'content' => 'In 2024, through a white label license agreement with Istanbul-based Uzman Şirketim, development work on the Trunçgil Parabol Accounting application and NES E-Invoice systems was completed and launched.',
|
||||
],
|
||||
'ru' => [
|
||||
'title' => 'Trunçgil Parabol Accounting',
|
||||
'content' => 'В 2024 году по лицензионному соглашению white label с компанией Uzman Şirketim (Стамбул) завершена разработка приложения Trunçgil Parabol Accounting и систем NES E-Invoice, продукты запущены в эксплуатацию.',
|
||||
],
|
||||
'de' => [
|
||||
'title' => 'Trunçgil Parabol Buchhaltung',
|
||||
'content' => 'Im Jahr 2024 wurde im Rahmen einer White-Label-Lizenzvereinbarung mit dem in Istanbul ansässigen Uzman Şirketim die Entwicklung der Trunçgil Parabol Buchhaltungsanwendung und der NES E-Rechnungssysteme abgeschlossen und veröffentlicht.',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($items as $itemData) {
|
||||
$translations = $itemData['translations'] ?? [];
|
||||
unset($itemData['translations']);
|
||||
|
||||
$item = CompanyHistoryItem::updateOrCreate(
|
||||
[
|
||||
'year' => $itemData['year'],
|
||||
'quarter' => $itemData['quarter'] ?? 'Q1',
|
||||
'sort_order' => $itemData['sort_order'],
|
||||
],
|
||||
array_merge($itemData, ['is_active' => true, 'position' => null])
|
||||
);
|
||||
|
||||
foreach ($translations as $langCode => $fields) {
|
||||
foreach ($fields as $fieldName => $value) {
|
||||
$item->setTranslation($fieldName, $langCode, $value, 'published');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Eski birleşik 2019 kaydı (sort_order çakışması) varsa kaldır
|
||||
CompanyHistoryItem::query()
|
||||
->where('year', 2019)
|
||||
->where('title', 'Teknopark ve Hackathon')
|
||||
->delete();
|
||||
|
||||
// Sıra numarası değişince kalan eski kayıtları temizle (yinelenen maddeleri önler)
|
||||
$expectedKeys = collect($items)
|
||||
->map(fn (array $item): string => $item['year'] . ':' . ($item['quarter'] ?? 'Q1') . ':' . $item['sort_order'])
|
||||
->flip();
|
||||
|
||||
CompanyHistoryItem::query()
|
||||
->get()
|
||||
->each(function (CompanyHistoryItem $item) use ($expectedKeys): void {
|
||||
$key = $item->year . ':' . ($item->quarter ?? 'Q1') . ':' . $item->sort_order;
|
||||
if (!$expectedKeys->has($key)) {
|
||||
$item->delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders\Concerns;
|
||||
|
||||
use App\Models\Page;
|
||||
use App\Models\SiteTranslation;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
trait SeedsSiteTranslations
|
||||
{
|
||||
/**
|
||||
* @param array<string, array<string, string>> $siteData
|
||||
*/
|
||||
protected function seedSiteTranslations(array $siteData): void
|
||||
{
|
||||
foreach ($siteData as $key => $translations) {
|
||||
$record = SiteTranslation::firstOrNew(['hash' => md5($key)]);
|
||||
$existing = $record->translations ?? [];
|
||||
|
||||
$merged = array_merge($existing, [
|
||||
'tr' => $translations['tr'] ?? $existing['tr'] ?? $key,
|
||||
], array_filter(
|
||||
$translations,
|
||||
fn ($value, $lang) => $value !== '' && $lang !== 'tr',
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
));
|
||||
|
||||
SiteTranslation::updateOrCreate(
|
||||
['hash' => md5($key)],
|
||||
[
|
||||
'key' => $key,
|
||||
'translations' => $merged,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, array<string, array<string, string>>> $pagesData
|
||||
*/
|
||||
protected function seedPageTranslations(array $pagesData): void
|
||||
{
|
||||
foreach ($pagesData as $pageKey => $fields) {
|
||||
$page = is_numeric($pageKey)
|
||||
? Page::query()->whereKey((int) $pageKey)->first()
|
||||
: Page::query()->where('slug', (string) $pageKey)->first();
|
||||
|
||||
if (! $page) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($fields as $fieldName => $translations) {
|
||||
foreach ($translations as $languageCode => $value) {
|
||||
if ($value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$page->setTranslation($fieldName, $languageCode, $value, 'published', 1);
|
||||
}
|
||||
|
||||
if (! isset($translations['tr'])) {
|
||||
$page->setTranslation($fieldName, 'tr', $page->{$fieldName}, 'published', 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function flushTranslationCache(): void
|
||||
{
|
||||
Cache::flush();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,16 @@ class DatabaseSeeder extends Seeder
|
||||
LanguageSeeder::class,
|
||||
SettingSeeder::class,
|
||||
PageSeeder::class,
|
||||
WebDevelopmentPageSeeder::class,
|
||||
AppDevelopmentPageSeeder::class,
|
||||
YapayZekaPageSeeder::class,
|
||||
TruncgilAkademiPageSeeder::class,
|
||||
SiteTranslationsSeeder::class,
|
||||
CompanyHistoryItemSeeder::class,
|
||||
DekupaiProductSeeder::class,
|
||||
FinanceProductSeeder::class,
|
||||
BlogSeeder::class,
|
||||
AwardSeeder::class,
|
||||
]);
|
||||
|
||||
// User::factory(10)->create();
|
||||
|
||||
@@ -0,0 +1,731 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Product;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DekupaiProductSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$title = [
|
||||
'tr' => 'Dekupai (Yapay Zeka Arka Plan Temizleme & Dekupe Stüdyosu)',
|
||||
'en' => 'Dekupai (AI Background Remover & Decoupage Studio)',
|
||||
'de' => 'Dekupai (KI-Hintergrundentferner & Dekoupage-Studio)',
|
||||
'ar' => 'Dekupai (استوديو عزل الخلفية والديكوباج بالذكاء الاصطناعي)',
|
||||
'se' => 'Dekupai (AI-bakgrundsborttagare & dekoupage-studio)',
|
||||
'ru' => 'Dekupai (ИИ для удаления фона и студия декупажа)',
|
||||
];
|
||||
|
||||
$content = [
|
||||
'tr' => '
|
||||
<section class="wrapper !bg-[#ffffff]">
|
||||
<div class="container py-14">
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss1.png" alt="BiRefNet Yapay Zeka Motoru" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Yerel Güç: BiRefNet Derin Öğrenme Motoru</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupai, yerel CPU/GPU birimleriniz üzerinde doğrudan çalışan **BiRefNet (Bilateral Reference Network)** modelini entegre eder. Verilerinizi hiçbir harici sunucuya veya bulut servisine göndermeden, %100 gizlilik ve sıfır veri sızıntısı riskiyle çalışırsınız. En ince detayları, saç tellerini ve yarı şeffaf nesneleri bile pürüzsüzce ayıklar.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-shield-check before:content-[\'ec47\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">%100 Yerel Çıkarım</h4>
|
||||
<p class="text-sm mb-0">İnternet bağlantısına ihtiyaç duymadan, yerel donanımınızın gücüyle arka planları kaldırın.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss2.png" alt="Gelişmiş Kırpma Editörü" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Profesyonel Kırpma ve Hizalama</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dahili **Cropper.js** entegrasyonu sayesinde isolasyon öncesi veya sonrası görsellerinizi dilediğiniz gibi boyutlandırın. Kare (Square), Hikaye (Story), Sinema (Cinema) ve Portre (Portrait) gibi hazır en boy oranlarını kullanabilir, 90 derece döndürme ve simetrik yansıtma işlemlerini anında gerçekleştirebilirsiniz.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-crop-alt-rotate before:content-[\'ea75\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Hazır En-Boy Şablonları</h4>
|
||||
<p class="text-sm mb-0">Sosyal medya platformları ve e-ticaret siteleri için hazır şablonlarla hızlıca boyutlandırın.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss3.png" alt="Auto-Trim Teknolojisi" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Tek Tıklamayla Kenar Kırpma (Auto-Trim)</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupai\'nin akıllı **Auto-Trim** algoritması, görseldeki boş şeffaf alanları otomatik olarak tarar. Subject izolasyonu bittiği an, görsel sınırlarını öznenizin dolu piksellerine tam sıfıra sıfır oturacak şekilde kırparak e-ticaret siteleriniz için optimize edilmiş ideal görseller üretir.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-expand-arrows before:content-[\'eae0\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Kusursuz Pixel Sınırları</h4>
|
||||
<p class="text-sm mb-0">Fazlalık şeffaf alanları tek tıkla silerek dosya boyutunu ve çerçeve yapısını en iyi hale getirin.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss4.png" alt="Backdrop ve Kompozitör Stüdyosu" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Arka Plan Kompozitörü & Sınırsız Renk</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">İzole ettiğiniz özneleri sadece şeffaf bırakmakla kalmayın! Dahili renk tekeri ve swatch paneli ile düz renkler seçin ya da önceden hazırlanmış göz alıcı **Premium Degradeler (Gradients)** atayarak saniyeler içinde etkileyici pazarlama görselleri ve afiş tasarımları oluşturun.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-palette before:content-[\'ec40\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Yaratıcı Degrade Tasarımlar</h4>
|
||||
<p class="text-sm mb-0">Öznelerinize anında şık arka planlar yerleştirerek profesyonel tasarımları tek elden çıkarın.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
',
|
||||
'en' => '
|
||||
<section class="wrapper !bg-[#ffffff]">
|
||||
<div class="container py-14">
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss1.png" alt="BiRefNet AI Engine" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Local Power: BiRefNet Deep Learning Engine</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupai integrates the state-of-the-art **BiRefNet (Bilateral Reference Network)** model, running entirely locally on your own CPU/GPU hardware. Work with 100% privacy and zero data leakage risk as no images are uploaded to external cloud services. Isolates thin hair strands and complex objects seamlessly.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-shield-check before:content-[\'ec47\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">100% Local Inference</h4>
|
||||
<p class="text-sm mb-0">Erase backgrounds locally using your device\'s hardware, offline with no internet needed.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss2.png" alt="Advanced Crop Editor" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Professional Cropping & Aspect Presets</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Shape and crop your isolated subjects before or after background removal using the integrated **Cropper.js** engine. Use quick preset ratios like Square, Story, Cinema, or Portrait, or rotate and flip your canvas symmetrically in one go.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-crop-alt-rotate before:content-[\'ea75\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Ready Aspect Ratios</h4>
|
||||
<p class="text-sm mb-0">Easily resize your assets for major social media platforms and digital storefronts.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss3.png" alt="Auto-Trim Technology" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">One-Click Border Crop (Auto-Trim)</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupai\'s smart **Auto-Trim** algorithm automatically scans the empty transparent border alpha values. The moment the extraction process concludes, it perfectly shrinks and fits the crop box right around the solid subject pixels, optimizing margins for e-commerce.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-expand-arrows before:content-[\'eae0\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Pixel-Perfect Margins</h4>
|
||||
<p class="text-sm mb-0">Maximize file compression and strip away unneeded blank areas instantly with a single button click.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss4.png" alt="Backdrop Compositor Studio" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Studio Backdrop & Gradient Compositor</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Don\'t settle for simple transparency! Paint beautiful flat solid backdrops using a quick color wheel, or assign eye-catching pre-configured **Premium Gradients** to transform isolated elements into high-impact marketing visuals in real-time.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-palette before:content-[\'ec40\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Premium Gradients Backdrop</h4>
|
||||
<p class="text-sm mb-0">Create outstanding graphics for promotions, cards, or catalogs on the fly without extra tools.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
',
|
||||
'de' => '
|
||||
<section class="wrapper !bg-[#ffffff]">
|
||||
<div class="container py-14">
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss1.png" alt="BiRefNet KI-Engine" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Lokale Leistung: BiRefNet Deep Learning Engine</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupai integriert das hochmoderne **BiRefNet (Bilateral Reference Network)**-Modell, das direkt auf Ihren lokalen CPU/GPU-Ressourcen läuft. Arbeiten Sie mit 100%iger Privatsphäre und ohne Risiko von Datenlecks, da keine Bilder auf externe Server hochgeladen werden. Selbst feinste Details wie Haare werden perfekt isoliert.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-shield-check before:content-[\'ec47\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">100% Lokale Berechnung</h4>
|
||||
<p class="text-sm mb-0">Entfernen Sie Hintergründe offline, vollständig lokal über die Hardware Ihres Computers.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss2.png" alt="Zuschneide-Editor" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Professionelles Zuschneiden & Seitenverhältnisse</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Passen Sie Ihre freigestellten Motive vor oder nach der Freistellung mit dem integrierten **Cropper.js**-Editor an. Nutzen Sie Vorlagen wie Quadrat (Square), Story, Kino (Cinema) und Porträt (Portrait) oder drehen Sie Ihr Bild im Handumdrehen.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-crop-alt-rotate before:content-[\'ea75\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Vordefinierte Vorlagen</h4>
|
||||
<p class="text-sm mb-0">Formatieren Sie Bilder mühelos für Social-Media-Kanäle und Online-Shops.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss3.png" alt="Auto-Trim Technologie" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Automatisches Trimmen der Kanten (Auto-Trim)</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Der intelligente **Auto-Trim**-Algorithmus von Dekupai scannt automatisch ungenutzte transparente Bereiche. Nach der Segmentierung wird der Zuschnittrahmen direkt an den gefüllten Pixeln des Objekts ausgerichtet – ideal für perfekt optimierte E-Commerce-Bilder.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-expand-arrows before:content-[\'eae0\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Präzise Pixelgrenzen</h4>
|
||||
<p class="text-sm mb-0">Überflüssige transparente Ränder mit einem Klick entfernen und die Dateigröße reduzieren.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss4.png" alt="Komposition und Studio" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Hintergrund-Compositor & Degradé-Studio</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Nutzen Sie mehr als nur Transparenz! Wählen Sie Volltonfarben über ein integriertes Farbrad oder weisen Sie atemberaubende **Premium-Farbverläufe** zu, um freigestellte Elemente in Sekundenschnelle in professionelle Werbematerialien zu verwandeln.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-palette before:content-[\'ec40\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Kreative Farbverläufe</h4>
|
||||
<p class="text-sm mb-0">Setzen Sie Ihr Produkt sofort in Szene, ganz ohne zusätzliche Grafikprogramme.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
',
|
||||
'se' => '
|
||||
<section class="wrapper !bg-[#ffffff]">
|
||||
<div class="container py-14">
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss1.png" alt="BiRefNet AI Motor" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Lokal kraft: BiRefNet AI-motor</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupai integrerar den banbrytande **BiRefNet (Bilateral Reference Network)**-modellen som körs direkt på dina egna lokala CPU/GPU-enheter. Du arbetar med 100% integritet och utan risk för dataläckor eftersom inga bilder skickas till externa molntjänster. Den tar bort bakgrunder med precision, även för fina hårstrån.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-shield-check before:content-[\'ec47\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">100% Lokal exekvering</h4>
|
||||
<p class="text-sm mb-0">Ta bort bakgrunder offline helt med hjälp av kraften i din lokala hårdvara.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss2.png" alt="Beskärningsverktyg" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Professionell beskärning och storleksmallar</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Forma och beskär dina isolerade objekt före eller efter bakgrundsborttagning med det integrerat **Cropper.js**-verktyget. Använd färdiga mallar som Kvadrat (Square), Story, Bio (Cinema) och Porträtt (Portrait), eller rotera bilderna enkelt.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-crop-alt-rotate before:content-[\'ea75\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Färdiga bildformat</h4>
|
||||
<p class="text-sm mb-0">Redigera snabbt dina bilder för sociala medier och e-handelsplattformar.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss3.png" alt="Auto-Trim teknik" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Automatisk kantbeskärning (Auto-Trim)</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupais intelligenta **Auto-Trim**-algoritm skannar automatiskt tomma transparenta områden. Så snart segmenteringen är klar beskärs bildgränserna perfekt runt objektets fyllda pixlar – perfekt för produktbilder inom e-handel.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-expand-arrows before:content-[\'eae0\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Perfekta pixelgränser</h4>
|
||||
<p class="text-sm mb-0">Ta bort onödigt tomrum med ett klick för att optimera filstorleken.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss4.png" alt="Bakgrundsverktyg" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Bakgrundskompositör & gradienter</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Nöj dig inte med bara transparens! Skapa vackra enfärgade bakgrunder med ett färgshjul eller lägg till iögonfallande **Premium-gradienter** för att förvandla dina urklippta bilder till färdigt marknadsföringsmaterial direkt.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-palette before:content-[\'ec40\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Kreativa bakgrunder</h4>
|
||||
<p class="text-sm mb-0">Skapa stilfulla bilder för dina produkter utan behov av externa grafikprogram.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
',
|
||||
'ar' => '
|
||||
<section class="wrapper !bg-[#ffffff]">
|
||||
<div class="container py-14">
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss1.png" alt="BiRefNet AI Engine" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">القوة المحلية: محرك التعلم العميق BiRefNet</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">يدمج Dekupai نموذج **BiRefNet (Bilateral Reference Network)** الأكثر تطوراً، والذي يعمل مباشرة على وحدات المعالجة المحلية (CPU/GPU) الخاصة بك. اعمل بخصوصية 100% ودون أي مخاطرة بتسريب البيانات لعدم رفع الصور إلى أي خادم خارجي. يعزل خصلات الشعر والتفاصيل الدقيقة بكل انسيابية.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-shield-check before:content-[\'ec47\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">معالجة محلية 100%</h4>
|
||||
<p class="text-sm mb-0">اعزل خلفيات صورك دون الحاجة للاتصال بالإنترنت وبالاعتماد الكامل على عتاد جهازك.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss2.png" alt="Cropper Editor" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">قص الصورة والتحكم في الأبعاد باحترافية</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">تتيح لك أداة **Cropper.js** المدمجة قص وتعديل مقاسات صورك بكل سهولة قبل أو بعد عزل الخلفية. يمكنك الاختيار من بين الأبعاد الجاهزة مثل المربع (Square)، والقصة (Story)، والسينما (Cinema)، والبورتريه (Portrait)، أو تدوير الصورة وعكسها بنقرة واحدة.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-crop-alt-rotate before:content-[\'ea75\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">قوالب أبعاد جاهزة</h4>
|
||||
<p class="text-sm mb-0">تعديل حجم صورك بسرعة وملاءمتها لمنصات التواصل الاجتماعي والمتاجر الإلكترونية.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss3.png" alt="Auto-Trim Technology" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">ملاءمة الحواف التلقائية بنقرة واحدة (Auto-Trim)</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">يقوم خوارزم ذكي مدمج **Auto-Trim** بمسح المناطق الشفافة الفارغة في الصورة تلقائياً. فور انتهاء عملية العزل، يتم ملاءمة حدود القص بدقة متناهية حول بكسلات العنصر المعزول، مما يمنحك صوراً مثالية ومهيأة للتجارة الإلكترونية.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-expand-arrows before:content-[\'eae0\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">حدود بكسل دقيقة</h4>
|
||||
<p class="text-sm mb-0">تخلص من المساحات الفارغة غير الضرورية لتقليص حجم الملف وتحسين أبعاد الإطار.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss4.png" alt="Backdrop Compositor Studio" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">استوديو تركيب الخلفيات والتدرجات اللونية</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">لا تكتفِ بالخلفية الشفافة! يمكنك اختيار ألوان موحدة لصورك عبر لوحة ألوان سريعة، أو تعيين تدرجات لونية مذهلة **Premium Gradients** لتصميم ملصقات دعائية وصور تسويقية فريدة في ثوانٍ معدودة.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-palette before:content-[\'ec40\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">خلفيات تدرج لوني مبتكرة</h4>
|
||||
<p class="text-sm mb-0">ضع منتجك في إطار احترافي ومبتكر دون الحاجة لاستخدام برامج تصميم خارجية.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
',
|
||||
'ru' => '
|
||||
<section class="wrapper !bg-[#ffffff]">
|
||||
<div class="container py-14">
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss1.png" alt="Нейросеть BiRefNet" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Локальная мощность: Нейросеть BiRefNet</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Dekupai интегрирует передовую нейросетевую модель **BiRefNet (Bilateral Reference Network)**, работающую локально на вашем процессоре или видеокарте. Ваши данные на 100% защищены, так как изображения не отправляются в облако. Нейросеть идеально вырезает мельчайшие детали, включая отдельные волоски.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-shield-check before:content-[\'ec47\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">100% локальная работа</h4>
|
||||
<p class="text-sm mb-0">Удаляйте фон без подключения к интернету, используя всю мощность вашего компьютера.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss2.png" alt="Умная обрезка" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Профессиональное кадрирование и пропорции</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Обрезайте и масштабируйте объекты до или после удаления фона благодаря встроенному редактору **Cropper.js**. Используйте готовые пресеты: квадрат (Square), сторис (Story), кино (Cinema) и портрет (Portrait), или отражайте холст по горизонтали/вертикали.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-crop-alt-rotate before:content-[\'ea75\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Готовые форматы</h4>
|
||||
<p class="text-sm mb-0">Быстро готовьте изображения для социальных сетей и маркетплейсов.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center mb-16">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-2">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss3.png" alt="Технология Auto-Trim" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full order-lg-1 pr-lg-10">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Умная обрезка пустых полей (Auto-Trim)</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Интеллектуальный алгоритм **Auto-Trim** сканирует прозрачные пиксели на границах. Сразу после удаления фона программа автоматически обрезает изображение точно по контуру вашего объекта, создавая идеальные карточки товаров.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-expand-arrows before:content-[\'eae0\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Идеальные границы объекта</h4>
|
||||
<p class="text-sm mb-0">Удаляйте лишние прозрачные поля в один клик для оптимизации размера файлов.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap mx-[-15px] items-center">
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full">
|
||||
<figure class="rounded-[0.8rem] shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.12)] overflow-hidden">
|
||||
<img src="/storage/products/dekupai-ss4.png" alt="Редактор фона" class="w-full h-auto">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="xl:w-6/12 lg:w-6/12 w-full flex-[0_0_auto] px-[15px] max-w-full pl-lg-10 mt-8 lg:mt-0">
|
||||
<h2 class="display-6 font-bold mb-4 !text-[1.8rem]">Редактор фона и премиум градиенты</h2>
|
||||
<p class="mb-6 text-gray-700 leading-[1.6]">Не ограничивайтесь прозрачным фоном! Заливайте подложку сплошным цветом или накладывайте стильные **Премиум-градиенты** для мгновенного создания ярких рекламных баннеров и постеров прямо в приложении.</p>
|
||||
<div class="flex items-start mb-3">
|
||||
<span class="btn btn-circle btn-soft-danger pointer-events-none mr-3 w-8 h-8 inline-flex items-center justify-center rounded-full"><i class="uil uil-palette before:content-[\'ec40\']"></i></span>
|
||||
<div>
|
||||
<h4 class="font-bold mb-1 text-[1rem]">Креативные градиенты</h4>
|
||||
<p class="text-sm mb-0">Создавайте потрясающий графический контент без использования сторонних программ.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
',
|
||||
];
|
||||
|
||||
$landingPageData = [
|
||||
'hero' => [
|
||||
'slogan' => 'Yapay Zeka ile Kusursuz Arka Plan Temizleme',
|
||||
'slogan_tr' => 'Yapay Zeka ile Kusursuz Arka Plan Temizleme',
|
||||
'slogan_en' => 'Flawless Background Removal with Local AI',
|
||||
'slogan_de' => 'Makellose Hintergrundentfernung mit lokaler KI',
|
||||
'slogan_ar' => 'إزالة مثالية للخلفية باستخدام الذكاء الاصطناعي المحلي',
|
||||
'slogan_se' => 'Felfri bakgrundsborttagning med lokal AI',
|
||||
'slogan_ru' => 'Безупречное удаление фона локальным ИИ',
|
||||
|
||||
'sub_slogan' => 'En karmaşık kenarları, saç tellerini ve şeffaf nesneleri yerel bilgisayarınızda saniyeler içinde ayıklayın.',
|
||||
'sub_slogan_tr' => 'En karmaşık kenarları, saç tellerini ve şeffaf nesneleri yerel bilgisayarınızda saniyeler içinde ayıklayın.',
|
||||
'sub_slogan_en' => 'Extract the most complex edges, hair strands, and translucent objects on your local machine in seconds.',
|
||||
'sub_slogan_de' => 'Trennen Sie komplexeste Kanten, Haare und transparente Objekte in Sekundenschnelle auf Ihrem PC.',
|
||||
'sub_slogan_ar' => 'اعزل خصلات الشعر والتفاصيل المعقدة والأجسام الشفافة محلياً على جهازك في ثوانٍ.',
|
||||
'sub_slogan_se' => 'Isolera komplexa kanter, hårstrån och transparenta föremål på din lokala dator på några sekunder.',
|
||||
'sub_slogan_ru' => 'Выделяйте самые сложные контуры, волосы и полупрозрачные объекты на локальном ПК за секунды.',
|
||||
|
||||
'background_image' => 'html/assets/img/photos/bg23.webp',
|
||||
'featured_image' => 'products/dekupai-ss1.png',
|
||||
'app_store_link' => '',
|
||||
'google_play_link' => '',
|
||||
],
|
||||
'features' => [
|
||||
[
|
||||
'title_tr' => 'BiRefNet Yapay Zeka Motoru',
|
||||
'title_en' => 'BiRefNet Neural Engine',
|
||||
'title_de' => 'BiRefNet-KI-Motor',
|
||||
'title_ar' => 'محرك BiRefNet الذكي',
|
||||
'title_se' => 'BiRefNet AI-motor',
|
||||
'title_ru' => 'Нейросеть BiRefNet',
|
||||
'icon' => 'processor',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
[
|
||||
'title_tr' => 'Akıllı Kırpma Editörü',
|
||||
'title_en' => 'Smart Cropping Studio',
|
||||
'title_de' => 'Intelligentes Zuschneiden',
|
||||
'title_ar' => 'استوديو القص الذكي',
|
||||
'title_se' => 'Smart beskärning',
|
||||
'title_ru' => 'Умная обрезка',
|
||||
'icon' => 'crop',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
[
|
||||
'title_tr' => 'Arka Plan Kompozitörü',
|
||||
'title_en' => 'Background Compositor',
|
||||
'title_de' => 'Hintergrund-Compositor',
|
||||
'title_ar' => 'تركيب الخلفيات',
|
||||
'title_se' => 'Bakgrundskompositör',
|
||||
'title_ru' => 'Редактор фона',
|
||||
'icon' => 'paint',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
[
|
||||
'title_tr' => 'Kayıpsız Dışa Aktarma',
|
||||
'title_en' => 'Lossless Format Export',
|
||||
'title_de' => 'Verlustfreier Export',
|
||||
'title_ar' => 'تصدير بدون فقدان الجودة',
|
||||
'title_se' => 'Förlustfri export',
|
||||
'title_ru' => 'Сохранение без потерь',
|
||||
'icon' => 'image',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
],
|
||||
'how_it_works' => [
|
||||
'download_image' => '',
|
||||
'download_form_label' => 'Dekupai Nasıl Çalışır?',
|
||||
'download_form_label_tr' => 'Dekupai Nasıl Çalışır?',
|
||||
'download_form_label_en' => 'How Dekupai Works',
|
||||
'download_form_label_de' => 'Wie funktioniert Dekupai?',
|
||||
'download_form_label_ar' => 'كيف يعمل Dekupai؟',
|
||||
'download_form_label_se' => 'Hur fungerar Dekupai?',
|
||||
'download_form_label_ru' => 'Как работает Dekupai?',
|
||||
'steps' => [
|
||||
[
|
||||
'number' => 1,
|
||||
'title_tr' => 'Görseli İçe Aktar',
|
||||
'title_en' => 'Import Image',
|
||||
'title_de' => 'Bild importieren',
|
||||
'title_ar' => 'رفع الصورة',
|
||||
'title_se' => 'Ladda upp bild',
|
||||
'title_ru' => 'Загрузите фото',
|
||||
'description_tr' => 'Dilediğiniz görseli sürükleyip bırakın veya göz atarak seçin.',
|
||||
'description_en' => 'Drag and drop any image or browse to select a local file.',
|
||||
'description_de' => 'Ziehen Sie ein Bild per Drag & Drop oder wählen Sie eine Datei aus.',
|
||||
'description_ar' => 'اسحب صورتك وأفلتها في منطقة السحب أو تصفح ملفاتك.',
|
||||
'description_se' => 'Dra och släpp en bild eller välj den från dina filer.',
|
||||
'description_ru' => 'Перетащите изображение в область загрузки или выберите файл.',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
[
|
||||
'number' => 2,
|
||||
'title_tr' => 'Tek Tıkla Arka Planı Kaldır',
|
||||
'title_en' => 'Extract with One Click',
|
||||
'title_de' => 'Mit einem Klick freistellen',
|
||||
'title_ar' => 'عزل بنقرة واحدة',
|
||||
'title_se' => 'Ta bort bakgrund',
|
||||
'title_ru' => 'Удалите фон в один клик',
|
||||
'description_tr' => 'BiRefNet yapay zeka modelini çalıştırarak subjecti anında ayıklayın.',
|
||||
'description_en' => 'Run the local BiRefNet deep learning model to instantly segment the subject.',
|
||||
'description_de' => 'Führen Sie das lokale BiRefNet-Modell aus, um das Motiv sofort freizustellen.',
|
||||
'description_ar' => 'شغل نموذج التعلم العميق لعزل العنصر بدقة متناهية في ثوانٍ.',
|
||||
'description_se' => 'Kör den lokala AI-modellen för att separera objektet direkt.',
|
||||
'description_ru' => 'Запустите локальную нейросеть для мгновенного удаления фона.',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
[
|
||||
'number' => 3,
|
||||
'title_tr' => 'Hizala & Kırp',
|
||||
'title_en' => 'Crop & Fine-Tune',
|
||||
'title_de' => 'Zuschneiden & Anpassen',
|
||||
'title_ar' => 'التعديل والقص',
|
||||
'title_se' => 'Justera & Redigera',
|
||||
'title_ru' => 'Кадрируйте и дорабатывайте',
|
||||
'description_tr' => 'En boy oranını sınırlayın veya Auto-Trim ile kenarlara tam oturtun.',
|
||||
'description_en' => 'Crop to aspect presets or snap margins securely around subject borders with Auto-Trim.',
|
||||
'description_de' => 'Wählen Sie Seitenverhältnisse oder passen Sie Kanten mit Auto-Trim an.',
|
||||
'description_ar' => 'تحكم في أبعاد الصورة وملاءمة حواف القص تلقائياً.',
|
||||
'description_se' => 'Välj bildformat eller anpassa kanterna med Auto-Trim.',
|
||||
'description_ru' => 'Выберите формат или автоматически обрежьте края с Auto-Trim.',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
[
|
||||
'number' => 4,
|
||||
'title_tr' => 'Stil Ver & Dışa Aktar',
|
||||
'title_en' => 'Style & Export',
|
||||
'title_de' => 'Stylen & Exportieren',
|
||||
'title_ar' => 'التصميم والتصدير',
|
||||
'title_se' => 'Stil & Exportera',
|
||||
'title_ru' => 'Оформляйте и сохраняйте',
|
||||
'description_tr' => 'Düz renk veya degradeler ekleyin, kayıpsız formatta kalitesini seçip kaydedin.',
|
||||
'description_en' => 'Add custom flat fills or premium gradients and export as PNG, JPEG, or WebP.',
|
||||
'description_de' => 'Fügen Sie Farben oder Verläufe hinzu und speichern Sie als PNG, JPEG oder WebP.',
|
||||
'description_ar' => 'أضف تدرجات لونية مميزة واحفظ الصورة بصيغة PNG أو JPEG أو WebP.',
|
||||
'description_se' => 'Välj bakgrundsfärg eller gradient och spara som PNG, JPEG eller WebP.',
|
||||
'description_ru' => 'Добавляйте градиенты и сохраняйте в PNG, JPEG или WebP.',
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
'video' => [
|
||||
'youtube_video_id' => '',
|
||||
'local_video_path' => 'assets/img/dekupai/usage.mp4',
|
||||
],
|
||||
'faqs' => [
|
||||
[
|
||||
'question_tr' => 'Dekupai görsellerimi buluta yüklüyor mu?',
|
||||
'question_en' => 'Does Dekupai upload my images to the cloud?',
|
||||
'question_de' => 'Lädt Dekupai meine Bilder in die Cloud hoch?',
|
||||
'question_ar' => 'هل يقوم Dekupai برفع صوري إلى السحابة؟',
|
||||
'question_se' => 'Laddar Dekupai upp mina bilder till molnet?',
|
||||
'question_ru' => 'Загружает ли Dekupai мои фотографии в облако?',
|
||||
'answer_tr' => 'Hayır. Dekupai, %100 yerel (local) çalışan bir masaüstü uygulamasıdır. Görsel işleme süreçlerinin tamamı kendi bilgisayarınızın donanım gücüyle yapılır. Verileriniz asla dışarı sızmaz ve internet bağlantısı olmadan da güvenle çalışır.',
|
||||
'answer_en' => 'No. Dekupai is a 100% local desktop application. All image segmentation tasks are processed entirely using your own device\'s hardware resources. Your assets never leave your computer and can be processed securely offline.',
|
||||
'answer_de' => 'Nein. Dekupai ist eine vollständig lokale Desktop-Anwendung. Alle Segmentierungsprozesse laufen direkt auf der Hardware Ihres PCs. Ihre Bilder verlassen niemals Ihren Computer und können offline verarbeitet werden.',
|
||||
'answer_ar' => 'لا. Dekupai هو تطبيق سطح مكتب يعمل محلياً بنسبة 100%. تتم جميع عمليات عزل وتعديل الصور باستخدام موارد جهازك دون رفعها إلى أي خادم، مما يضمن أمانها التام وإمكانية العمل بدون اتصال بالإنترنت.',
|
||||
'answer_se' => 'Nej. Dekupai är en 100% lokal skrivbordsapplikation. Alla bildbehandlingsprocesser körs direkt på din egen dators hårdvara. Dina bilder lämnar aldrig din enhet och kan redigeras helt offline.',
|
||||
'answer_ru' => 'Нет. Dekupai работает на 100% локально на вашем ПК. Все процессы обработки изображений выполняются ресурсами вашего компьютера. Ваши файлы никуда не отправляются и защищены от утечек.',
|
||||
'question' => '',
|
||||
'answer' => '',
|
||||
],
|
||||
[
|
||||
'question_tr' => 'BiRefNet AI modeli hangi nesneleri ayıklayabilir?',
|
||||
'question_en' => 'What objects can the BiRefNet AI model segment?',
|
||||
'question_de' => 'Welche Objekte kann das BiRefNet-KI-Modell freistellen?',
|
||||
'question_ar' => 'ما هي الأشياء التي يمكن لنموذج BiRefNet عزلها؟',
|
||||
'question_se' => 'Vilka objekt kan BiRefNet AI-modellen isolera?',
|
||||
'question_ru' => 'Какие объекты может выделять модель BiRefNet?',
|
||||
'answer_tr' => 'BiRefNet; insanlar, hayvanlar, ürünler, e-ticaret nesneleri, araçlar ve bitkiler gibi geniş bir yelpazedeki özneleri yüksek hassasiyetle tanır. Özellikle karmaşık saç telleri, tüyler ve şeffaf/cam nesnelerin kenarlarını kusursuzca ayırt eder.',
|
||||
'answer_en' => 'BiRefNet is trained to segment a wide variety of subjects including humans, pets, e-commerce products, vehicles, and plants. It excels at separating complex edges like fine hair strands, fur, and translucent or glass materials.',
|
||||
'answer_de' => 'BiRefNet wurde entwickelt, um eine Vielzahl von Motiven freizustellen: Menschen, Haustiere, Produkte, Fahrzeuge und Pflanzen. Es glänzt besonders beim Isolieren komplexer Kanten wie feiner Haare, Fell oder transparenter Materialien.',
|
||||
'answer_ar' => 'يتميز نموذج BiRefNet بدقة متناهية في عزل مختلف العناصر مثل الأشخاص، والحيوانات الأليفة، ومنتجات المتاجر الإلكترونية، والسيارات، والنباتات. ويتفوق بشكل خاص في تمييز التفاصيل المعقدة كخصلات الشعر الناعمة والزجاج والأجسام الشفافة.',
|
||||
'answer_se' => 'BiRefNet känner igen en mängd olika objekt som människor, djur, produkter, fordon och växter med hög precision. Den är särskilt bra på att isolera komplexa konturer som hårstrån, päls och transparenta material.',
|
||||
'answer_ru' => 'BiRefNet отлично распознает широкий спектр объектов: людей, животных, товары для маркетплейсов, автомобили и растения. Модель превосходно справляется со сложными контурами, такими как волосы, шерсть или прозрачные материалы.',
|
||||
'question' => '',
|
||||
'answer' => '',
|
||||
],
|
||||
[
|
||||
'question_tr' => 'Auto-Trim özelliği bana ne kazandırır?',
|
||||
'question_en' => 'What does the Auto-Trim feature offer?',
|
||||
'question_de' => 'Was bietet die Auto-Trim-Funktion?',
|
||||
'question_ar' => 'ما الفائدة التي تقدمها لي ميزة Auto-Trim؟',
|
||||
'question_se' => 'Vad ger Auto-Trim-funktionen för fördelar?',
|
||||
'question_ru' => 'Что дает функция Auto-Trim?',
|
||||
'answer_tr' => 'Auto-Trim, izolasyon sonrası görselin çevresindeki boş şeffaf (alfa) pikselleri milimetrik olarak kırpar. Böylece görseliniz öznenin sınırlarına tam oturur. Özellikle e-ticaret siteleri, kataloğu ve mobil uygulamalar için mükemmel uyumlu ürün görselleri oluşturur.',
|
||||
'answer_en' => 'Auto-Trim dynamically scans empty transparent alpha boundaries and snaps the crop frame tight against the outermost solid pixels of your subject. This is highly useful for generating neatly aligned and standardized product listings for online storefronts.',
|
||||
'answer_de' => 'Auto-Trim scannt transparente Ränder und richtet das Zuschneidefenster exakt an den äußersten gefüllten Pixeln Ihres Motivs aus. Dies ist äußerst nützlich, um einheitlich ausgerichtete und standardisierte Produktbilder für Webshops zu erstellen.',
|
||||
'answer_ar' => 'تقom ميزة Auto-Trim بمسح الحدود الشفافة الفارغة في الصورة وملاءمة إطار القص تماماً حول البكسلات الفعلية للعنصر المعزول. هذا مفيد للغاية لإنشاء صور منتجات موحدة ومرتبة للمتاجر الرقمية.',
|
||||
'answer_se' => 'Auto-Trim skannar tomma transparenta områden och anpassar beskärningsrutan exakt efter objektets yttersta pixlar. Detta är mycket användbart för att skapa enhetliga och snygga produktbilder för webbshoppar.',
|
||||
'answer_ru' => 'Auto-Trim автоматически сканирует прозрачные поля и сжимает рамку кадрирования точно по крайним видимым пикселям объекта. Это незаменимо для создания аккуратных и стандартизированных карточек товаров для интернет-магазинов.',
|
||||
'question' => '',
|
||||
'answer' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
Product::updateOrCreate(
|
||||
['slug' => 'dekupai'],
|
||||
[
|
||||
'product_category_id' => 1, // Software
|
||||
'title' => $title,
|
||||
'type' => 'product',
|
||||
'hero_image' => 'products/dekupai-logo.png',
|
||||
'content' => $content,
|
||||
'landing_page_data' => $landingPageData,
|
||||
'view_template' => null,
|
||||
'external_url' => null,
|
||||
'sort_order' => 1,
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\ProductCategory;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FinanceProductSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
// 1. Download the logo from https://finance.truncgil.com/img/logo.svg
|
||||
$logoUrl = 'https://finance.truncgil.com/img/logo.svg';
|
||||
$logoFileName = 'products/truncgil-finance-logo.svg';
|
||||
|
||||
try {
|
||||
// Check if directory exists in the public disk
|
||||
if (!Storage::disk('public')->exists('products')) {
|
||||
Storage::disk('public')->makeDirectory('products');
|
||||
}
|
||||
|
||||
// Get logo contents
|
||||
$context = stream_context_create([
|
||||
"ssl" => [
|
||||
"verify_peer" => false,
|
||||
"verify_peer_name" => false,
|
||||
],
|
||||
"http" => [
|
||||
"header" => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)\r\n"
|
||||
]
|
||||
]);
|
||||
$logoContents = file_get_contents($logoUrl, false, $context);
|
||||
|
||||
if ($logoContents !== false) {
|
||||
Storage::disk('public')->put($logoFileName, $logoContents);
|
||||
$this->command?->info('Downloaded and stored the Trunçgil Finance logo successfully.');
|
||||
} else {
|
||||
$this->command?->error('Could not download logo contents.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->command?->error('Failed to download Trunçgil Finance logo: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 2. Prepare translatable fields
|
||||
$title = [
|
||||
'tr' => 'Trunçgil Finance (Canlı Döviz, Altın ve Kripto Para API Servisi)',
|
||||
'en' => 'Trunçgil Finance (Live Currency, Gold, and Crypto API Service)',
|
||||
];
|
||||
|
||||
$content = [
|
||||
'tr' => 'Trunçgil Finance, altın, döviz ve kripto para verilerini anlık ve gecikmesiz olarak sunan yüksek performanslı bir API servisidir. Kurumsal uygulamalarınızdan mobil projelerinize kadar her ölçekte güvenle entegre edebilirsiniz.',
|
||||
'en' => 'Trunçgil Finance is a high-performance API service offering instantaneous and lag-free gold, currency, and cryptocurrency data. Securely integrate into your projects of any scale, from enterprise apps to mobile platforms.',
|
||||
];
|
||||
|
||||
// 3. Prepare detailed landing page data structure
|
||||
$landingPageData = [
|
||||
'hero' => [
|
||||
'slogan_tr' => 'Yüksek Performanslı Altın, Döviz ve Kripto API Servisi',
|
||||
'slogan_en' => 'High-Performance Gold, Currency, and Crypto API Service',
|
||||
'sub_slogan_tr' => 'Anlık finansal verileri, güçlü altyapımızla projelerinize gecikmesiz entegre edin. Geliştirici dostu entegrasyon ve sıfır kesinti.',
|
||||
'sub_slogan_en' => 'Integrate real-time financial data into your applications lag-free with our robust infrastructure. Developer-friendly and 100% uptime.',
|
||||
'featured_image' => $logoFileName,
|
||||
],
|
||||
'features' => [
|
||||
[
|
||||
'title_tr' => 'Gecikmesiz Canlı Akış',
|
||||
'title_en' => 'Lag-Free Live Streams',
|
||||
'icon' => 'processor',
|
||||
],
|
||||
[
|
||||
'title_tr' => 'Geliştirici Dostu SDK & API',
|
||||
'title_en' => 'Developer-Friendly SDK & API',
|
||||
'icon' => 'crop',
|
||||
],
|
||||
[
|
||||
'title_tr' => 'Güvenli ve Kesintisiz Servis',
|
||||
'title_en' => 'Secure & Uptime Gurantee',
|
||||
'icon' => 'safe',
|
||||
],
|
||||
[
|
||||
'title_tr' => 'Zengin Veri Kaynakları',
|
||||
'title_en' => 'Rich Data Sources',
|
||||
'icon' => 'cloud',
|
||||
],
|
||||
],
|
||||
'how_it_works' => [
|
||||
'download_form_label_tr' => 'Nasıl Entegre Edilir?',
|
||||
'download_form_label_en' => 'How to Integrate?',
|
||||
'steps' => [
|
||||
[
|
||||
'number' => 1,
|
||||
'title_tr' => 'API Anahtarınızı Alın',
|
||||
'title_en' => 'Get Your API Key',
|
||||
'description_tr' => 'Saniyeler içinde kayıt olun ve size özel güvenli API anahtarınızı oluşturun.',
|
||||
'description_en' => 'Sign up in seconds and generate your secure, dedicated API key.',
|
||||
],
|
||||
[
|
||||
'number' => 2,
|
||||
'title_tr' => 'Dokümantasyonu İnceleyin',
|
||||
'title_en' => 'Browse Documentation',
|
||||
'description_tr' => 'Her dilde hazırlanmış örnek kodlar ve şemalarla entegrasyon adımlarını inceleyin.',
|
||||
'description_en' => 'Check integration steps with sample codes and schemas available in every language.',
|
||||
],
|
||||
[
|
||||
'number' => 3,
|
||||
'title_tr' => 'Kodunuza Dahil Edin',
|
||||
'title_en' => 'Integrate Into Code',
|
||||
'description_tr' => 'Hazır kütüphanelerimizle veya RESTful uç noktalarımızla verileri projenize çekin.',
|
||||
'description_en' => 'Fetch data using our client SDKs or standard RESTful HTTP endpoints.',
|
||||
],
|
||||
[
|
||||
'number' => 4,
|
||||
'title_tr' => 'Canlıya Alın & İzleyin',
|
||||
'title_en' => 'Go Live & Monitor',
|
||||
'description_tr' => 'Kullanım istatistiklerinizi panelimizden izleyin ve yüksek hızın tadını çıkarın.',
|
||||
'description_en' => 'Track your request statistics in real-time from the dashboard and enjoy lightning speed.',
|
||||
],
|
||||
],
|
||||
],
|
||||
'faqs' => [
|
||||
[
|
||||
'question_tr' => 'Veriler ne sıklıkla güncelleniyor?',
|
||||
'question_en' => 'How frequently is the data updated?',
|
||||
'answer_tr' => 'Finansal verilerimiz doğrudan global ve yerel borsalardan anlık akış olarak alınmakta ve milisaniyeler bazında güncellenmektedir.',
|
||||
'answer_en' => 'Our financial feeds are retrieved directly from global and regional exchanges and updated in real-time on a millisecond scale.',
|
||||
],
|
||||
[
|
||||
'question_tr' => 'Ücretsiz deneme sürümü mevcut mu?',
|
||||
'question_en' => 'Is there a free tier available?',
|
||||
'answer_tr' => 'Evet. Trunçgil Finance geliştiriciler için belirli sayıda aylık istek limitine sahip tamamen ücretsiz bir sandbox planı sunmaktadır.',
|
||||
'answer_en' => 'Yes. Trunçgil Finance offers a completely free sandbox tier with a monthly request allowance for developers to test and build.',
|
||||
],
|
||||
[
|
||||
'question_tr' => 'Hangi veri türlerini sunuyorsunuz?',
|
||||
'question_en' => 'What kinds of financial data do you offer?',
|
||||
'answer_tr' => 'Uluslararası döviz pariteleri, serbest piyasa ve banka altın fiyatları, kıymetli madenler ve tüm popüler kripto para birimleri veri setlerimizin parçasıdır.',
|
||||
'answer_en' => 'Global currency exchange rates, free-market and banking gold values, precious metals, and all major cryptocurrency pairs are fully covered.',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// 4. Determine category
|
||||
$category = ProductCategory::first();
|
||||
$categoryId = $category ? $category->id : 1;
|
||||
|
||||
// 5. Update or Create the Product
|
||||
Product::updateOrCreate(
|
||||
['slug' => 'truncgil-finance'],
|
||||
[
|
||||
'product_category_id' => $categoryId,
|
||||
'title' => $title,
|
||||
'type' => 'product',
|
||||
'hero_image' => $logoFileName,
|
||||
'content' => $content,
|
||||
'landing_page_data' => $landingPageData,
|
||||
'view_template' => 'front.products.finance',
|
||||
'external_url' => 'https://finance.truncgil.com/',
|
||||
'sort_order' => 2,
|
||||
'is_active' => true,
|
||||
]
|
||||
);
|
||||
|
||||
$this->command?->info('✅ Trunçgil Finance product seeded successfully!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class HomeMenuTranslationsSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$this->call(SiteTranslationsSeeder::class);
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class LanguageSeeder extends Seeder
|
||||
'native_name' => 'Deutsch',
|
||||
'flag' => '🇩🇪',
|
||||
'direction' => 'ltr',
|
||||
'is_active' => false,
|
||||
'is_active' => true,
|
||||
'is_default' => false,
|
||||
'sort_order' => 3,
|
||||
],
|
||||
@@ -55,14 +55,24 @@ class LanguageSeeder extends Seeder
|
||||
'sort_order' => 4,
|
||||
],
|
||||
[
|
||||
'code' => 'ar',
|
||||
'name' => 'Arabic',
|
||||
'native_name' => 'العربية',
|
||||
'flag' => '🇸🇦',
|
||||
'direction' => 'rtl',
|
||||
'is_active' => false,
|
||||
'code' => 'se',
|
||||
'name' => 'Swedish',
|
||||
'native_name' => 'Svenska',
|
||||
'flag' => '🇸🇪',
|
||||
'direction' => 'ltr',
|
||||
'is_active' => true,
|
||||
'is_default' => false,
|
||||
'sort_order' => 5,
|
||||
'sort_order' => 6,
|
||||
],
|
||||
[
|
||||
'code' => 'ru',
|
||||
'name' => 'Russian',
|
||||
'native_name' => 'Русский',
|
||||
'flag' => '🇷🇺',
|
||||
'direction' => 'ltr',
|
||||
'is_active' => true,
|
||||
'is_default' => false,
|
||||
'sort_order' => 7,
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Page;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class PageSeeder extends Seeder
|
||||
@@ -13,9 +14,9 @@ class PageSeeder extends Seeder
|
||||
public function run(): void
|
||||
{
|
||||
// İlk kullanıcıyı bul veya oluştur
|
||||
$author = \App\Models\User::first();
|
||||
$author = User::query()->first();
|
||||
if (!$author) {
|
||||
$author = \App\Models\User::create([
|
||||
$author = User::query()->create([
|
||||
'name' => 'Admin',
|
||||
'email' => 'admin@truncgil.com',
|
||||
'password' => bcrypt('password'),
|
||||
@@ -66,9 +67,9 @@ class PageSeeder extends Seeder
|
||||
'author_id' => $author->id,
|
||||
'parent_id' => $corporate->id,
|
||||
'title' => 'Hakkımızda',
|
||||
'excerpt' => 'Biz kimiz?',
|
||||
'content' => '<p>Hakkımızda içeriği...</p>',
|
||||
'template' => 'generic',
|
||||
'excerpt' => 'Trunçgil Teknoloji olarak yazılım, e-ticaret, sağlık teknolojileri ve eğitim alanlarında kalite odaklı, uzun vadeli çözümler sunuyoruz.',
|
||||
'content' => '<p>Trunçgil Teknoloji olarak 2014 yılından bu yana yazılım, e-ticaret, sağlık teknolojileri ve eğitim alanlarında yenilikçi çözümler üretiyoruz.</p>',
|
||||
'template' => 'hakkimizda',
|
||||
'status' => 'published',
|
||||
'is_homepage' => false,
|
||||
'show_in_menu' => true,
|
||||
@@ -77,6 +78,24 @@ class PageSeeder extends Seeder
|
||||
]
|
||||
);
|
||||
|
||||
// 2.1.2 Ödüllerimiz (Child of Kurumsal)
|
||||
Page::updateOrCreate(
|
||||
['slug' => 'odullerimiz'],
|
||||
[
|
||||
'author_id' => $author->id,
|
||||
'parent_id' => $corporate->id,
|
||||
'title' => 'Ödüllerimiz',
|
||||
'excerpt' => 'Yenilikçi teknolojilerimizle sektöre yön veriyor, başarılarimizi küresel ödüllerle taçlandırıyoruz.',
|
||||
'content' => '<p>Yenilikçi teknolojilerimizle sektöre yön veriyor, başarılarimizi küresel ödüllerle taçlandırıyoruz.</p>',
|
||||
'template' => 'odullerimiz',
|
||||
'status' => 'published',
|
||||
'is_homepage' => false,
|
||||
'show_in_menu' => true,
|
||||
'sort_order' => 6, // Sort order among Kurumsal children
|
||||
'published_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
// 2.2 Vizyon & Misyon (Child of Kurumsal)
|
||||
Page::updateOrCreate(
|
||||
['slug' => 'vizyon-misyon'],
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Page;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PartnersPageSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* @var array<int, array{name: string, url: string, slug: string, simple_icon?: string}>
|
||||
*/
|
||||
protected array $partners = [
|
||||
// Türkiye — domain, hosting ve dijital altyapı
|
||||
['name' => 'Turhost', 'url' => 'https://www.turhost.com', 'slug' => 'turhost'],
|
||||
['name' => 'Natro', 'url' => 'https://www.natro.com', 'slug' => 'natro'],
|
||||
['name' => 'İsimtescil', 'url' => 'https://www.isimtescil.net', 'slug' => 'isimtescil'],
|
||||
['name' => 'Radore', 'url' => 'https://www.radore.com', 'slug' => 'radore'],
|
||||
['name' => 'Nic.tr', 'url' => 'https://www.nic.tr', 'slug' => 'nic-tr'],
|
||||
['name' => 'Türk Telekom', 'url' => 'https://www.turktelekom.com.tr', 'slug' => 'turk-telekom'],
|
||||
['name' => 'Bulutistan', 'url' => 'https://www.bulutistan.com', 'slug' => 'bulutistan'],
|
||||
['name' => 'Güzel Hosting', 'url' => 'https://www.guzel.net.tr', 'slug' => 'guzel-hosting'],
|
||||
['name' => 'DorukNet', 'url' => 'https://www.doruk.net', 'slug' => 'doruknet'],
|
||||
|
||||
// Global teknoloji ortakları
|
||||
['name' => 'Google', 'url' => 'https://www.google.com', 'slug' => 'google', 'simple_icon' => 'google'],
|
||||
['name' => 'Google Gemini', 'url' => 'https://gemini.google.com', 'slug' => 'google-gemini', 'simple_icon' => 'googlegemini'],
|
||||
['name' => 'Apple', 'url' => 'https://www.apple.com', 'slug' => 'apple', 'simple_icon' => 'apple'],
|
||||
['name' => 'Meta', 'url' => 'https://www.meta.com', 'slug' => 'meta', 'simple_icon' => 'meta'],
|
||||
['name' => 'Cursor', 'url' => 'https://www.cursor.com', 'slug' => 'cursor'],
|
||||
['name' => 'Microsoft Azure', 'url' => 'https://azure.microsoft.com', 'slug' => 'microsoft-azure', 'simple_icon' => 'microsoftazure'],
|
||||
['name' => 'Cloudflare', 'url' => 'https://www.cloudflare.com', 'slug' => 'cloudflare', 'simple_icon' => 'cloudflare'],
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$author = User::query()->first();
|
||||
if (!$author) {
|
||||
$this->command?->warn('Kullanıcı bulunamadı, seeder atlandı.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$corporate = Page::query()->where('slug', '=', 'kurumsal')->first();
|
||||
|
||||
Storage::disk('public')->makeDirectory('partners', 0755, true);
|
||||
|
||||
$partnerRecords = [];
|
||||
|
||||
foreach ($this->partners as $partner) {
|
||||
$logoPath = $this->downloadLogo($partner);
|
||||
|
||||
if (!$logoPath) {
|
||||
$this->command?->warn("Logo indirilemedi: {$partner['name']}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$partnerRecords[] = [
|
||||
'name' => $partner['name'],
|
||||
'url' => $partner['url'],
|
||||
'logo' => $logoPath,
|
||||
];
|
||||
|
||||
$this->command?->info("✓ {$partner['name']}");
|
||||
}
|
||||
|
||||
Page::updateOrCreate(
|
||||
['slug' => 'cozum-ortaklari'],
|
||||
[
|
||||
'author_id' => $author->id,
|
||||
'parent_id' => $corporate?->id,
|
||||
'title' => 'Çözüm Ortakları',
|
||||
'excerpt' => 'Türkiye\'nin önde gelen domain, hosting ve dijital altyapı sağlayıcıları ile Google, Apple, Meta, Cursor ve Gemini gibi global teknoloji liderleriyle güçlü bir ekosistem.',
|
||||
'content' => '<p>Güçlü çözüm ortaklıklarımız sayesinde müşterilerimize daha hızlı, daha yenilikçi ve daha sürdürülebilir çözümler sunuyoruz. Alan adı ve hosting hizmetlerinden bulut altyapısına, yapay zeka platformlarından kurumsal yazılım ekosistemlerine kadar geniş bir partner ağıyla projelerinizi güvenle hayata geçiriyoruz.</p>',
|
||||
'template' => 'corporate.partners',
|
||||
'status' => 'published',
|
||||
'is_homepage' => false,
|
||||
'show_in_menu' => true,
|
||||
'sort_order' => 7,
|
||||
'published_at' => now(),
|
||||
'meta_title' => 'Çözüm Ortaklarımız | Trunçgil',
|
||||
'meta_description' => 'Domain, hosting, veri merkezi ve bulut altyapısı ortaklarımız ile Google, Apple, Meta, Cursor ve Gemini gibi global teknoloji iş birliklerimiz.',
|
||||
'data' => [
|
||||
'partners' => $partnerRecords,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->command?->info('✅ ' . count($partnerRecords) . ' çözüm ortağı veritabanına kaydedildi.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{name: string, url: string, slug: string, simple_icon?: string} $partner
|
||||
*/
|
||||
protected function downloadLogo(array $partner): ?string
|
||||
{
|
||||
$filename = 'partners/' . $partner['slug'] . '.png';
|
||||
$fullPath = Storage::disk('public')->path($filename);
|
||||
|
||||
$domain = parse_url($partner['url'], PHP_URL_HOST);
|
||||
$domain = $domain ? preg_replace('/^www\./', '', $domain) : null;
|
||||
|
||||
$sources = [];
|
||||
|
||||
if (!empty($partner['simple_icon'])) {
|
||||
$sources[] = 'https://cdn.simpleicons.org/' . $partner['simple_icon'];
|
||||
}
|
||||
|
||||
if ($domain) {
|
||||
$sources[] = 'https://logo.clearbit.com/' . $domain;
|
||||
$sources[] = 'https://www.google.com/s2/favicons?domain=' . $domain . '&sz=128';
|
||||
}
|
||||
|
||||
foreach ($sources as $source) {
|
||||
try {
|
||||
$response = Http::timeout(15)
|
||||
->withHeaders(['User-Agent' => 'Mozilla/5.0 (compatible; TruncgilSeeder/1.0)'])
|
||||
->get($source);
|
||||
|
||||
if (!$response->successful() || strlen($response->body()) < 200) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$body = $response->body();
|
||||
$image = @imagecreatefromstring($body);
|
||||
|
||||
if ($image === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$width = imagesx($image);
|
||||
$height = imagesy($image);
|
||||
|
||||
$canvas = imagecreatetruecolor(256, 100);
|
||||
imagealphablending($canvas, false);
|
||||
imagesavealpha($canvas, true);
|
||||
$transparent = imagecolorallocatealpha($canvas, 255, 255, 255, 127);
|
||||
imagefill($canvas, 0, 0, $transparent);
|
||||
imagealphablending($canvas, true);
|
||||
|
||||
$scale = min(220 / max($width, 1), 72 / max($height, 1));
|
||||
$newWidth = (int) max(1, round($width * $scale));
|
||||
$newHeight = (int) max(1, round($height * $scale));
|
||||
$offsetX = (int) ((256 - $newWidth) / 2);
|
||||
$offsetY = (int) ((100 - $newHeight) / 2);
|
||||
|
||||
imagecopyresampled($canvas, $image, $offsetX, $offsetY, 0, 0, $newWidth, $newHeight, $width, $height);
|
||||
imagepng($canvas, $fullPath, 8);
|
||||
|
||||
imagedestroy($image);
|
||||
imagedestroy($canvas);
|
||||
|
||||
return $filename;
|
||||
} catch (\Throwable) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->generatePlaceholder($partner, $fullPath) ? $filename : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{name: string, url: string, slug: string} $partner
|
||||
*/
|
||||
protected function generatePlaceholder(array $partner, string $fullPath): bool
|
||||
{
|
||||
$canvas = imagecreatetruecolor(256, 100);
|
||||
imagealphablending($canvas, false);
|
||||
imagesavealpha($canvas, true);
|
||||
|
||||
$bg = imagecolorallocatealpha($canvas, 248, 250, 252, 0);
|
||||
imagefill($canvas, 0, 0, $bg);
|
||||
|
||||
$textColor = imagecolorallocate($canvas, 30, 41, 59);
|
||||
$initials = Str::upper(Str::substr(Str::before($partner['name'], ' '), 0, 2));
|
||||
|
||||
$font = 5;
|
||||
$textWidth = imagefontwidth($font) * strlen($initials);
|
||||
$textHeight = imagefontheight($font);
|
||||
imagestring($canvas, $font, (int) ((256 - $textWidth) / 2), (int) ((100 - $textHeight) / 2), $initials, $textColor);
|
||||
|
||||
$result = imagepng($canvas, $fullPath, 8);
|
||||
imagedestroy($canvas);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Proposal;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProposalSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$markdown = <<<'EOT'
|
||||
# TRUNÇGİL TEKNOLOJİ SANAYİ VE TİCARET LİMİTED ŞİRKETİ
|
||||
|
||||
<div align="center">
|
||||
<br/>
|
||||
<h1>YAZILIM GELİŞTİRME HİZMET ALIMI TEKNİK VE MALİ TEKLİF DOKÜMANI</h1>
|
||||
<h3>Acil Servis Triaj ve Erken Tedavi İçin Çok Ajanlı Büyük Dil Modeli Tabanlı Hekim Karar Destek Sistemi Projesi</h3>
|
||||
<br/>
|
||||
<p><strong>DOKÜMAN REFERANS NO:</strong> TT-2026-DEEPP-001-R1</p>
|
||||
<p><strong>TEKLİF TARİHİ:</strong> 25 Mayıs 2026</p>
|
||||
<p><strong>GEÇERLİLİK TARİHİ:</strong> 26 Haziran 2026</p>
|
||||
<p><strong>SON TESLİM ZAMANI:</strong> 26.05.2026 Salı - Saat 16:00</p>
|
||||
<br/>
|
||||
<hr/>
|
||||
<br/>
|
||||
<p><strong>HAZIRLAYAN YÜKLENİCİ</strong></p>
|
||||
<p><strong>Trunçgil Teknoloji Sanayi ve Ticaret Limited Şirketi</strong><br/>
|
||||
Gaziantep Üniversitesi Teknoparkı, Şahinbey / Gaziantep<br/>
|
||||
<a href="https://www.truncgil.com.tr" target="_blank">www.truncgil.com.tr</a> | <a href="mailto:info@truncgil.com.tr">info@truncgil.com.tr</a></p>
|
||||
<br/>
|
||||
<p><strong>MÜŞTERİ / YETKİLİ KURUM</strong></p>
|
||||
<p><strong>Deepp Yapay Zeka Teknolojileri A.Ş.</strong><br/>
|
||||
Cevizlik Mah. İstanbul Cad. No:12-26 Bakırköy / İstanbul<br/>
|
||||
<a href="mailto:info@deepp.ai">info@deepp.ai</a></p>
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
> [!NOTE]
|
||||
> **Gizlilik Bildirimi**
|
||||
> Bu doküman ve ekleri, Trunçgil Teknoloji Sanayi ve Ticaret Limited Şirketi’nin ticari sırlarını ve özel teknik yaklaşımlarını içermekte olup, Deepp Yapay Zeka Teknolojileri A.Ş. yetkililerinin değerlendirmesi için özel olarak hazırlanmıştır. Üçüncü şahıslarla paylaşılması veya çoğaltılması izne tabidir.
|
||||
|
||||
---
|
||||
|
||||
## 1. Kurumsal Profil ve Resmi Bilgiler
|
||||
|
||||
Trunçgil Teknoloji, kökleri 2014 yılına dayanan ve 2019 yılından bu yana Gaziantep Teknopark (Teknoloji Geliştirme Bölgesi) bünyesinde AR-GE faaliyetleri yürüten kurumsal bir teknoloji şirketidir. Şirketimiz; yüksek ölçeklenebilir backend sistemleri (**Laravel & PHP 8.3+ & Laravel Octane**), Laravel ile en entegre çalışan yüksek performanslı modern web portalları (**Inertia.js & Vue 3 & Tailwind CSS**) ile cross-platform mobil uygulamalar (**Flutter & Dart**) alanında uçtan uca uzmanlaşmıştır.
|
||||
|
||||
Özellikle tıbbi veri entegrasyonu, kişisel verilerin korunması (KVKK/GDPR), HIPAA uyumluluğu, gerçek zamanlı veri akışları ve yapay zeka entegrasyonlu hibrit yazılım mimarilerinde derin tecrübeye sahibiz.
|
||||
|
||||
### Resmi Bilgilerimiz
|
||||
|
||||
| Alan | Resmi Bilgi Tanımı |
|
||||
| :--- | :--- |
|
||||
| **Firma Ünvanı** | TRUNÇGİL TEKNOLOJİ SANAYİ VE TİCARET LİMİTED ŞİRKETİ |
|
||||
| **Web Sitesi** | [www.truncgil.com.tr](https://www.truncgil.com.tr) |
|
||||
| **Yetkili İsim & Soyisim** | Ümit Tunç (Kurucu / Software Engineer) |
|
||||
| **Yetkili Telefon** | +90 551 234 36 95 |
|
||||
| **E-posta** | [info@truncgil.com.tr](mailto:info@truncgil.com.tr) / [umit.tunc@truncgil.com](mailto:umit.tunc@truncgil.com) |
|
||||
| **Yazışma Adresi** | Çamtepe Mah. Mahmut Tevfik Atay Bul. Gaziantep Teknopark No: 4A İç Kapı No: 1 Şahinbey / Gaziantep |
|
||||
| **Vergi Dairesi & No** | Şahinbey V.D. - 8591492435 |
|
||||
| **MERSİS No** | 0859149243500001 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Proje Anlayışı ve Kapsam
|
||||
|
||||
Deepp Yapay Zeka Teknolojileri A.Ş. tarafından geliştirilen **"Acil Servis Triaj ve Erken Tedavi İçin Çok Ajanlı Büyük Dil Modeli Tabanlı Hekim Karar Destek Sistemi"** projesinin nihai başarısı; karmaşık yapay zeka ajanlarının, klinik iş akışlarına kusursuz ve gecikmesiz (low latency) bir şekilde entegre edilmesine bağlıdır.
|
||||
|
||||
Şirketimiz, bu şartname kapsamındaki **üç ana kalemden (Backend, Mobil Arayüz, Web Arayüzü)** oluşan yazılım altyapısının geliştirilmesini üstlenecektir. Bu kapsamda, **Backend mimarisi Laravel**, **İstemci arayüzlerinden Mobil tarafı Flutter**, **Web arayüzü (Komuta ve Yönetim Portalı) ise Laravel ile en entegre çalışan Inertia.js + Vue.js (Vue 3)** mimarisiyle geliştirilecektir.
|
||||
|
||||
### Kapsam Matrisi
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph TRUNCGIL_TEKNOLOJI_KAPSAMI ["Trunçgil Teknoloji Geliştirme Kapsamı"]
|
||||
BE["1. Laravel Tabanlı Yüksek Performanslı<br/>Backend & Ajan Orkestratör Katmanı"]
|
||||
MOB["2. Cross-Platform Flutter Mobil Uygulaması<br/>(iOS & Android)"]
|
||||
WEB["3. Laravel ile Entegre Inertia.js & Vue 3 Web Portalı<br/>(Masaüstü & Tablet Uyumlu)"]
|
||||
QA["4. Kapsamlı Test, Yük Testi (500 RPS) &<br/>Güvenlik (Penetrasyon) Testleri"]
|
||||
PILOT["5. 3 Aylık Pilot Hastane Kurulumu &<br/>UAT Desteği"]
|
||||
SLA["6. 6 Aylık Klinik Doğrulama Boyunca<br/>7/24 SLA ve Bakım Hizmeti"]
|
||||
end
|
||||
|
||||
subgraph DEEPP_AI_KAPSAMI ["Deepp Yapay Zeka A.Ş. Kapsamı"]
|
||||
AI_Ajanlari["Çok Ajanlı LLM Katmanı<br/>(Triaj, Risk Skorlama, Tedavi Önerisi,<br/>Kontrendikasyon, Sesli Asistan)"]
|
||||
LLM_API["Yapay Zeka Modelleri ve<br/>Yüksek Performanslı Altyapı"]
|
||||
end
|
||||
|
||||
BE -->|Entegrasyon & Veri Akışı| AI_Ajanlari
|
||||
|
||||
style DEEPP_AI_KAPSAMI fill:#FFF1F2,stroke:#FDA4AF,stroke-width:1.5px,color:#9F1239,rx:12px,ry:12px
|
||||
style TRUNCGIL_TEKNOLOJI_KAPSAMI fill:#EEF2FF,stroke:#A5B4FC,stroke-width:1.5px,color:#3730A3,rx:12px,ry:12px
|
||||
style AI_Ajanlari fill:#FFFFFF,stroke:#F43F5E,stroke-width:1.5px,color:#9F1239,rx:8px,ry:8px
|
||||
style LLM_API fill:#FFFFFF,stroke:#F43F5E,stroke-width:1.5px,color:#9F1239,rx:8px,ry:8px
|
||||
style BE fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style MOB fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style WEB fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style QA fill:#FFFFFF,stroke:#6366F1,stroke-width:1.2px,color:#3730A3,rx:8px,ry:8px
|
||||
style PILOT fill:#FFFFFF,stroke:#6366F1,stroke-width:1.2px,color:#3730A3,rx:8px,ry:8px
|
||||
style SLA fill:#FFFFFF,stroke:#6366F1,stroke-width:1.2px,color:#3730A3,rx:8px,ry:8px
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Kapsam Dışı Hususlar:** Yapay zeka ajanlarının (LLM) algoritma tasarımı, LLM model eğitimi/ince ayarı ve ana çıkarım (inference) motorunun geliştirilmesi bu şartnamenin ve teklifimizin kapsamı dışındadır. Ancak, bu modellerin backend sistemine entegrasyonu, güvenli ve maskelenmiş veri akışının sağlanması ve ajansal orkestrasyon katmanının kurulması teklifimizin asli unsurudur.
|
||||
|
||||
---
|
||||
|
||||
## 3. Teknik Mimari ve Çözüm Yaklaşımı
|
||||
|
||||
Sistemin saniyede 500 eşzamanlı isteği (500 RPS) minimum gecikmeyle karşılayabilmesi ve yüksek güvenlik (KVKK %100 maskeleme) standartlarını sağlayabilmesi amacıyla kuracağımız modern tıbbi yazılım mimarisi aşağıda detaylandırılmıştır.
|
||||
|
||||
### Mimari Bileşenler:
|
||||
* **Yüksek Performanslı Laravel Backend & API Gateway:** **PHP 8.3+** ve **Laravel Octane (Swoole / RoadRunner)** altyapısı kullanılarak PHP'nin geleneksel bootstrap yükü sıfıra indirilerek saniyede binlerce istek (RPS) karşılanacaktır.
|
||||
* **Orkestratör Katmanı & Ajan İletişimi:** Çok ajanlı mimarinin yönetiminde, asenkron işlemler için **Laravel Queue & Laravel Horizon** ile entegre edilmiş **RabbitMQ** kullanılacaktır.
|
||||
* **Flutter Mobil & Inertia.js + Vue 3 Web Front-end:** Mobil tarafı **Flutter & Dart** ile derlenecektir. Web komuta portalı ise **Inertia.js + Vue 3** mimarisiyle kurulacaktır.
|
||||
* **Konteyner Orkestrasyonu & Veritabanı:** Tüm sistem Dockerize edilerek **Kubernetes (K8s)** üzerinde kurulacaktır. Veritabanı olarak normalize edilmiş, yedekli **PostgreSQL** tercih edilecektir.
|
||||
|
||||
---
|
||||
|
||||
### MİMARİ DİYAGRAM 1: Uçtan Uca Sistem Mimarisi (C4 Model Container Level)
|
||||
|
||||
Laravel backend ve Flutter istemcilerinden oluşan yapay zeka entegrasyonlu sistem mimarisi:
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph İstemci_Katmanı [Flutter Mobil & Vue 3 Web Katmanı]
|
||||
mobile[Flutter Mobil App - Dart]
|
||||
web[Inertia.js & Vue 3 Web Portal - Tailwind CSS]
|
||||
voip[VoIP / SIP Telefon Ağ Geçidi]
|
||||
end
|
||||
|
||||
subgraph Güvenlik_ve_Yönlendirme [Gateway & Security]
|
||||
gateway[Laravel API Gateway - Octane / Sanctum / Passport RBAC]
|
||||
end
|
||||
|
||||
subgraph Uygulama_Servisleri_Laravel [Laravel & PHP 8.3+ Uygulama & Orkestrasyon]
|
||||
orchestrator[Laravel Ajan Orkestratör Katmanı - PHP Pipelines]
|
||||
anonymizer[KVKK Veri Anonimleştirme Servisi]
|
||||
stt_service[Arka Plan Gürültü Filtreli VoIP / STT Servisi]
|
||||
audit_logger[Şifreli Tıbbi Denetim İzi Kaydedici - Laravel Auditing]
|
||||
db_service[Eloquent ORM Veri Erişim Katmanı]
|
||||
end
|
||||
|
||||
subgraph Mesaj_Kuyruğu_ve_Önbellek [Queue & Cache]
|
||||
mq[RabbitMQ / Laravel Horizon - Redis]
|
||||
cache[Redis Cache / Session Store]
|
||||
websockets[Laravel Reverb - WebSocket Server]
|
||||
end
|
||||
|
||||
subgraph Veritabanı_Katmanı [Veritabanı Katmanı]
|
||||
db[(PostgreSQL - Yedekli / Normalize SQL)]
|
||||
end
|
||||
|
||||
subgraph Harici_Servisler [Deepp AI Çok Ajanlı LLM Sistemi]
|
||||
triage_agent[Triaj Ajanı]
|
||||
risk_agent[Klinik Risk Skorlama Ajanı]
|
||||
treatment_agent[Tedavi Önerisi Ajanı]
|
||||
contra_agent[Kontrendikasyon Ajanı]
|
||||
end
|
||||
|
||||
mobile -->|HTTPS / WSS| gateway
|
||||
web -->|HTTPS / WSS| gateway
|
||||
voip -->|SIP / VoIP API| stt_service
|
||||
|
||||
gateway -->|Internal REST / gRPC| anonymizer
|
||||
gateway -->|Internal REST / gRPC| orchestrator
|
||||
|
||||
anonymizer -->|Kişisel Verilerden Arındırılmış| orchestrator
|
||||
orchestrator -->|Laravel Jobs / Dispatch| mq
|
||||
mq -->|Ajan İletişimi| triage_agent
|
||||
mq -->|Ajan İletişimi| risk_agent
|
||||
mq -->|Ajan İletişimi| treatment_agent
|
||||
mq -->|Ajan İletişimi| contra_agent
|
||||
|
||||
orchestrator -->|Hızlı Okuma| cache
|
||||
orchestrator -->|WebSocket Push| websockets
|
||||
websockets -->|Canlı Komuta Paneli Güncellemesi| web
|
||||
websockets -->|Kritik Push Bildirim| mobile
|
||||
orchestrator -->|Eloquent Models| db_service
|
||||
db_service --> db
|
||||
db_service -->|Milisaniye Zaman Damgası & SHA256| audit_logger
|
||||
audit_logger --> db
|
||||
|
||||
style İstemci_Katmanı fill:#F0FDFA,stroke:#99F6E4,stroke-width:1.5px,color:#115E59,rx:12px,ry:12px
|
||||
style Güvenlik_ve_Yönlendirme fill:#FFFBEB,stroke:#FDE68A,stroke-width:1.5px,color:#92400E,rx:12px,ry:12px
|
||||
style Uygulama_Servisleri_Laravel fill:#EEF2FF,stroke:#C7D2FE,stroke-width:1.5px,color:#3730A3,rx:12px,ry:12px
|
||||
style Mesaj_Kuyruğu_ve_Önbellek fill:#FAF5FF,stroke:#E9D5FF,stroke-width:1.5px,color:#6B21A8,rx:12px,ry:12px
|
||||
style Veritabanı_Katmanı fill:#ECFDF5,stroke:#A7F3D0,stroke-width:1.5px,color:#065F46,rx:12px,ry:12px
|
||||
style Harici_Servisler fill:#FFF1F2,stroke:#FECDD3,stroke-width:1.5px,color:#9F1239,rx:12px,ry:12px
|
||||
style mobile fill:#FFFFFF,stroke:#0D9488,stroke-width:1.5px,color:#115E59,rx:8px,ry:8px
|
||||
style web fill:#FFFFFF,stroke:#0D9488,stroke-width:1.5px,color:#115E59,rx:8px,ry:8px
|
||||
style voip fill:#FFFFFF,stroke:#0D9488,stroke-width:1.5px,color:#115E59,rx:8px,ry:8px
|
||||
style gateway fill:#FFFFFF,stroke:#D97706,stroke-width:1.5px,color:#92400E,rx:8px,ry:8px
|
||||
style orchestrator fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style anonymizer fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style stt_service fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style audit_logger fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style db_service fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.5px,color:#3730A3,rx:8px,ry:8px
|
||||
style mq fill:#FFFFFF,stroke:#9333EA,stroke-width:1.5px,color:#6B21A8,rx:8px,ry:8px
|
||||
style cache fill:#FFFFFF,stroke:#9333EA,stroke-width:1.5px,color:#6B21A8,rx:8px,ry:8px
|
||||
style websockets fill:#FFFFFF,stroke:#9333EA,stroke-width:1.5px,color:#6B21A8,rx:8px,ry:8px
|
||||
style db fill:#FFFFFF,stroke:#059669,stroke-width:1.5px,color:#065F46,rx:8px,ry:8px
|
||||
style triage_agent fill:#FFFFFF,stroke:#F43F5E,stroke-width:1.5px,color:#9F1239,rx:8px,ry:8px
|
||||
style risk_agent fill:#FFFFFF,stroke:#F43F5E,stroke-width:1.5px,color:#9F1239,rx:8px,ry:8px
|
||||
style treatment_agent fill:#FFFFFF,stroke:#F43F5E,stroke-width:1.5px,color:#9F1239,rx:8px,ry:8px
|
||||
style contra_agent fill:#FFFFFF,stroke:#F43F5E,stroke-width:1.5px,color:#9F1239,rx:8px,ry:8px
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### MİMARİ DİYAGRAM 2: Gerçek Zamanlı ve Asenkron Veri Akışı & Gecikme Bütçesi
|
||||
|
||||
Saniyede 500 istek (500 RPS) kapasitesi altında Laravel Octane ve Flutter istemcileri arasındaki milisaniye seviyesindeki gecikme bütçesi (Latency Budget) hedefi:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Hekim as Hekim Arayüzü (Flutter Mobil / Vue 3 Web)
|
||||
participant GW as Laravel API Gateway (Octane)
|
||||
participant ANON as Laravel Anonimleştirme Servisi
|
||||
participant ORCH as Laravel Orkestratör Katmanı (Octane / Swoole)
|
||||
participant MQ as Laravel Horizon (Redis / RabbitMQ)
|
||||
participant AI as Yapay Zeka Ajanları (LLM)
|
||||
|
||||
Note over Hekim,GW: Senkron Sorgu Akışı (Hedef: < 200 ms - LLM hariç)
|
||||
Hekim->>GW: 1. İstek Gönderimi (Sanctum Auth + PHI Verileri)
|
||||
rect rgb(240, 248, 255)
|
||||
GW->>ANON: 2. PHI Verisini Anonimleştir (Hedef: < 25 ms)
|
||||
ANON-->>GW: 3. Maskelenmiş Veri
|
||||
end
|
||||
GW->>ORCH: 4. İstek Yönlendirme (Hedef: < 15 ms)
|
||||
|
||||
rect rgb(255, 240, 245)
|
||||
Note over ORCH,AI: Asenkron Ajan Orkestrasyonu (Hedef: < 500 ms)
|
||||
ORCH->>MQ: 5. Ajan Görevlerini Kuyruğa Ekle (Laravel Job Dispatch)
|
||||
MQ->>AI: 6. Ajanlar Arası Mesajlaşma & Paralel Çalışma (gRPC / HTTP)
|
||||
AI-->>MQ: 7. Ajan Sonuçları (Triaj, Risk, Tedavi vb.)
|
||||
MQ-->>ORCH: 8. Sonuçları Konsolide Et
|
||||
end
|
||||
|
||||
ORCH-->>GW: 9. Konsolide Edilmiş Yanıt (Hedef: < 40 ms)
|
||||
GW-->>Hekim: 10. Yanıtı İlet (JSON formatında)
|
||||
|
||||
Note over Hekim,ORCH: Laravel Reverb WebSocket Canlı Akışı (Gecikme < 500 ms)
|
||||
ORCH->>Hekim: 11. Canlı Triaj & Komuta Güncellemesi (WSS Push)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### MİMARİ DİYAGRAM 3: KVKK Veri Anonimleştirme Servisi Pipeline
|
||||
|
||||
Kişisel Sağlık Verilerinin (PHI) %100 doğrulukla maskelenmesi ve dış LLM uç noktalarına sızıntının önlenmesi için iki katmanlı anonimleştirme iş akışı:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph Girdi [Girdi Verisi]
|
||||
raw[Ham Tıbbi Veri: Ad/Soyad, T.C., Yaş, Telefon, Özel Öykü]
|
||||
end
|
||||
|
||||
subgraph Maskeleme_Motoru [Laravel Veri Anonimleştirme Servisi]
|
||||
parser[1. Veri Ayrıştırıcı - JSON Parser]
|
||||
|
||||
subgraph Katman_1 [Katman 1: PHP Regex & NER Kütüphaneleri]
|
||||
regex[Düzenli İfadeler - Regex: TC No, Telefon, Mail, Ad/Soyad]
|
||||
nlp_ner[PHP NLP NER Modeli: Kurum Adları, Tarihler, Şehirler]
|
||||
end
|
||||
|
||||
subgraph Katman_2 [Katman 2: Şifreleme & Sözlük Map]
|
||||
salt_hash[SHA-256 Tuzlanmış Hash: Hasta ID -> Maskeli ID]
|
||||
dict_map[Geçici Oturum Sözlüğü - Secure Vault / Redis]
|
||||
end
|
||||
|
||||
reconstructor[3. Maskelenmiş Paket Oluşturucu]
|
||||
end
|
||||
|
||||
subgraph Çıktı [Güvenli Çıktı]
|
||||
clean_data[Yapay Zeka Uç Noktalarına Gönderilen %100 Anonim Veri]
|
||||
end
|
||||
|
||||
raw --> parser
|
||||
parser --> regex
|
||||
parser --> nlp_ner
|
||||
regex --> salt_hash
|
||||
nlp_ner --> salt_hash
|
||||
salt_hash --> dict_map
|
||||
dict_map --> reconstructor
|
||||
reconstructor --> clean_data
|
||||
|
||||
style Girdi fill:#FFF7ED,stroke:#FED7AA,stroke-width:1.5px,color:#C2410C,rx:12px,ry:12px
|
||||
style Maskeleme_Motoru fill:#F5F3FF,stroke:#DDD6FE,stroke-width:1.5px,color:#5B21B6,rx:12px,ry:12px
|
||||
style Katman_1 fill:#EEF2FF,stroke:#C7D2FE,stroke-width:1.2px,color:#3730A3,rx:10px,ry:10px
|
||||
style Katman_2 fill:#FAF5FF,stroke:#E9D5FF,stroke-width:1.2px,color:#6B21A8,rx:10px,ry:10px
|
||||
style Çıktı fill:#ECFDF5,stroke:#A7F3D0,stroke-width:1.5px,color:#065F46,rx:12px,ry:12px
|
||||
style raw fill:#FFFFFF,stroke:#F97316,stroke-width:1.5px,color:#C2410C,rx:8px,ry:8px
|
||||
style parser fill:#FFFFFF,stroke:#7C3AED,stroke-width:1.5px,color:#5B21B6,rx:8px,ry:8px
|
||||
style regex fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.2px,color:#3730A3,rx:8px,ry:8px
|
||||
style nlp_ner fill:#FFFFFF,stroke:#4F46E5,stroke-width:1.2px,color:#3730A3,rx:8px,ry:8px
|
||||
style salt_hash fill:#FFFFFF,stroke:#9333EA,stroke-width:1.2px,color:#6B21A8,rx:8px,ry:8px
|
||||
style dict_map fill:#FFFFFF,stroke:#9333EA,stroke-width:1.2px,color:#6B21A8,rx:8px,ry:8px
|
||||
style reconstructor fill:#FFFFFF,stroke:#7C3AED,stroke-width:1.5px,color:#5B21B6,rx:8px,ry:8px
|
||||
style clean_data fill:#FFFFFF,stroke:#10B981,stroke-width:1.5px,color:#065F46,rx:8px,ry:8px
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Teknik Şartname Uyum Matrisi
|
||||
|
||||
Teknik şartnamedeki maddelere olan uyumumuz:
|
||||
|
||||
| Şartname Maddesi | Talep Edilen Gereksinim | Uyum Durumu | Trunçgil Teknoloji Teknik Çözüm Yaklaşımı |
|
||||
| :--- | :--- | :---: | :--- |
|
||||
| **3. Backend - 1** | Saniyede en az 500 eşzamanlı istek (500 RPS) kapasitesi. | **TAM UYUM** | **Laravel Octane (Swoole / RoadRunner)** altyapısı, Kubernetes yatay otomatik ölçekleme (HPA) ve Redis önbellek katmanı ile 500 RPS yük altında darboğazsız çalışacaktır. |
|
||||
| **3. Backend - 2** | Ses/yazı inputları, LLM ve istemciler arası milisaniye (ms) seviyesinde gecikme bütçesi. | **TAM UYUM** | HTTP/2, WebSocket ve gRPC / HTTP REST protokolleri kullanılarak ağ gecikmesi minimize edilecek, asenkron yapıda veri akışları kurulacaktır. |
|
||||
| **3. Backend - 8** | %100 oranında kişisel sağlık verilerini maskeleyecek "Veri Anonimleştirme Servisi". | **TAM UYUM** | Özel geliştirilecek PHP Regex + NLP NER (Adlandırılmış Varlık Tanıma) kütüphaneleriyle veriler uç noktalara gitmeden önce maskelenecektir. |
|
||||
| **3. Backend - 9** | VoIP API telefon entegrasyonunda yanıt süresi maksimum 2 saniye (2000 ms). | **TAM UYUM** | SIP/RTP ses paketleri gerçek zamanlı (chunked) işlenecek, Laravel Event Listeners ve asenkron kuyruk yapısı ile 2 saniyenin altında yanıt dönülecektir. |
|
||||
|
||||
---
|
||||
|
||||
## 7. Proforma Fatura ve Mali Teklif
|
||||
|
||||
Trunçgil Teknoloji’nin Teknopark bünyesindeki faaliyetleri kapsamında hazırlanan resmi mali teklifimiz ve proforma faturamız aşağıda sunulmuştur.
|
||||
|
||||
### 📜 PROFORMA FATURA (PROFORMA INVOICE)
|
||||
|
||||
| **Proforma No:** TT-2026-PF-042-R1 | **Tarih:** 25.05.2026 |
|
||||
| :--- | :--- |
|
||||
| **Satıcı (Yüklenici):** TRUNÇGİL TEKNOLOJİ SAN. VE TİC. LTD. ŞTİ. | **Alıcı (Müşteri):** DEEPP YAPAY ZEKA TEKNOLOJİLERİ A.Ş. |
|
||||
| **Adres:** Gaziantep Teknopark No: 4A İç Kapı No: 1 Şahinbey / Gaziantep | **Adres:** Cevizlik Mah. İstanbul Cad. No:12-26 Bakırköy / İstanbul |
|
||||
|
||||
---
|
||||
|
||||
#### Bütçe Dağılım Tablosu
|
||||
|
||||
| Sıra No | Hizmet / Kalem Açıklaması | Süre (Ay) | Efor (Adam/Saat) | Birim Fiyat (TL) | Toplam Tutar (TL - KDV Hariç) | KDV Oranı (%) | KDV Tutarı (TL) |
|
||||
| :---: | :--- | :---: | :---: | :---: | :---: | :---: | :---: |
|
||||
| **1** | **Backend (Laravel) Geliştirme Hizmeti** | 12 | 1.200 | 1.000,00 TL | 1.200.000,00 TL | %0 | 0,00 TL |
|
||||
| **2** | **Mobil Arayüz (Flutter Mobile) Geliştirme Hizmeti** | 12 | 960 | 1.000,00 TL | 960.000,00 TL | %0 | 0,00 TL |
|
||||
| **3** | **Web Arayüz (Inertia.js + Vue.js) Geliştirme Hizmeti** | 12 | 880 | 1.000,00 TL | 880.000,00 TL | %0 | 0,00 TL |
|
||||
|
||||
---
|
||||
|
||||
#### Mali Özet
|
||||
|
||||
* **Ara Toplam (KDV Hariç):** **3.040.000,00 TL** *(Üç Milyon Kırk Bin Türk Lirası)*
|
||||
* **Toplam KDV (%0 - Muaf):** **0,00 TL**
|
||||
* **GENEL TOPLAM:** **3.040.000,00 TL** *(Üç Milyon Kırk Bin Türk Lirası)*
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Yasal KDV Muafiyeti Maddesi:**
|
||||
> Satıcı firma (Trunçgil Teknoloji), Gaziantep Teknopark bünyesinde AR-GE faaliyeti yürüten tescilli bir kuruluştur. **4691 Sayılı Teknoloji Geliştirme Bölgeleri Kanunu** Geçici 2. Maddesi uyarınca, bölgede üretilen yazılımların teslim ve hizmetleri **KDV'den istisnadır (%0 KDV)**. Bu muafiyet, alıcı firmaya doğrudan **%20 maliyet avantajı** sağlamaktadır.
|
||||
|
||||
---
|
||||
|
||||
## 8. Sonuç ve Taahhüt
|
||||
|
||||
Trunçgil Teknoloji olarak, **Deepp AI** projenizin medikal standartlara uygun, güvenli, yüksek performanslı ve geleceğe hazır bir altyapı üzerinde yükselmesi için gerekli tüm tecrübe, teknik yetkinlik ve kurumsal disipline sahibiz.
|
||||
|
||||
**Saygılarımızla,**
|
||||
|
||||
**Ümit Tunç**
|
||||
Software Engineer & Kurucu
|
||||
**Trunçgil Teknoloji Sanayi ve Ticaret Limited Şirketi**
|
||||
EOT;
|
||||
|
||||
Proposal::updateOrCreate(
|
||||
['slug' => 'tt-2026-deepp-001'],
|
||||
[
|
||||
'title' => 'Yazılım Geliştirme Hizmet Alımı Teknik ve Mali Teklif Dokümanı',
|
||||
'client_name' => 'Deepp Yapay Zeka Teknolojileri A.Ş.',
|
||||
'client_email' => 'info@deepp.ai',
|
||||
'content' => $markdown,
|
||||
'total_price' => 3040000.00,
|
||||
'currency' => 'TRY',
|
||||
'status' => 'sent',
|
||||
'valid_until' => '2026-06-26',
|
||||
'meta' => [
|
||||
'accent_color' => 'emerald',
|
||||
'show_calculator' => true
|
||||
],
|
||||
'views_count' => 42
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Database\Seeders\Concerns\SeedsSiteTranslations;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class SiteTranslationsSeeder extends Seeder
|
||||
{
|
||||
use SeedsSiteTranslations;
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
protected array $dataFiles = [
|
||||
'home_menu_translations.php',
|
||||
'home_menu_locale_patch.php',
|
||||
'neler_yapariz_translations.php',
|
||||
'app_development_translations.php',
|
||||
'web_development_translations.php',
|
||||
'corporate_translations.php',
|
||||
'misc_site_translations.php',
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
foreach ($this->dataFiles as $file) {
|
||||
$path = database_path('data/' . $file);
|
||||
|
||||
if (! is_file($path)) {
|
||||
$this->command?->warn("Translation data file not found: {$file}");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var array{site?: array<string, array<string, string>>, pages?: array<string|int, array<string, array<string, string>>>} $data */
|
||||
$data = require $path;
|
||||
|
||||
if (! empty($data['site'])) {
|
||||
$this->seedSiteTranslations($data['site']);
|
||||
}
|
||||
|
||||
if (! empty($data['pages'])) {
|
||||
$this->seedPageTranslations($data['pages']);
|
||||
}
|
||||
|
||||
$this->command?->info("Applied translations from {$file}");
|
||||
}
|
||||
|
||||
$this->flushTranslationCache();
|
||||
|
||||
$this->command?->info('All site translations applied successfully.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Page;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class TruncgilAkademiPageSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
$author = User::query()->first();
|
||||
if (!$author) {
|
||||
$this->command?->warn('Kullanıcı bulunamadı, seeder atlandı.');
|
||||
return;
|
||||
}
|
||||
|
||||
$corporate = Page::where('slug', 'kurumsal')->first();
|
||||
|
||||
Page::updateOrCreate(
|
||||
['slug' => 'truncgil-akademi'],
|
||||
[
|
||||
'author_id' => $author->id,
|
||||
'parent_id' => $corporate?->id,
|
||||
'title' => 'Trunçgil Akademi',
|
||||
'excerpt' => 'Trunçgil Akademi uzman kadrosuyla web uygulamaları, yapay zeka, Vibe Coding ve müzik prodüksiyonu gibi geleceğin popüler alanlarında profesyonel eğitimler sunar.',
|
||||
'content' => '',
|
||||
'template' => 'services.truncgil-akademi',
|
||||
'featured_image' => null,
|
||||
'custom_header_blade' => 'partials.header-truncgil-akademi',
|
||||
'status' => 'published',
|
||||
'is_homepage' => false,
|
||||
'show_in_menu' => true,
|
||||
'sort_order' => 20,
|
||||
'published_at' => now(),
|
||||
'meta_title' => 'Trunçgil Akademi | Geleceğin Teknolojileri ve Eğitimleri',
|
||||
'meta_description' => 'Trunçgil Akademi ile Vibe Coding, Yapay Zeka, Web Uygulama Geliştirme ve Müzik Prodüksiyonu eğitimleri. Uzmanından uygulamalı ve modern müfredat.',
|
||||
]
|
||||
);
|
||||
|
||||
$this->command?->info('✅ Trunçgil Akademi sayfası başarıyla oluşturuldu.');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user