diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8d99b854 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +.git +.gitea +.codex +.env +.env.* +**/.dart_tool +**/.packages +**/build +**/node_modules +**/dist +**/.next +**/.cache +**/coverage +**/tmp +**/logs +**/*.log +**/*.swp +**/.DS_Store diff --git a/.gitea/workflows/code_check.yml b/.gitea/workflows/code_check.yml index 3c159b23..c296e360 100644 --- a/.gitea/workflows/code_check.yml +++ b/.gitea/workflows/code_check.yml @@ -1,118 +1,125 @@ name: Code Check on: - workflow_dispatch: - inputs: - run_lint: - description: "Run linters for Go and Flutter" - required: true - type: boolean - default: true - run_backend_tests: - description: "Run backend Go tests" - required: true - type: boolean - default: true - run_userfront_tests: - description: "Run userfront Flutter tests" - required: true - type: boolean - default: true + pull_request: + branches: + - dev + workflow_dispatch: + inputs: + run_lint: + description: "Run linters for Go and Flutter" + required: true + type: boolean + default: true + run_backend_tests: + description: "Run backend Go tests" + required: true + type: boolean + default: true + run_userfront_tests: + description: "Run userfront Flutter tests" + required: true + type: boolean + default: true jobs: - lint: - if: ${{ inputs.run_lint == true }} - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 + lint: + if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_lint == true }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20" + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" - - name: i18n resource check - run: | - node tools/i18n-scanner/index.js + - name: i18n resource check + run: | + mkdir -p reports + node tools/i18n-scanner/index.js + node tools/i18n-scanner/report.js + cat reports/i18n-report.txt - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.25" - cache-dependency-path: backend/go.sum + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache-dependency-path: backend/go.sum - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - cache: true + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true - - name: Lint Go backend - uses: golangci/golangci-lint-action@v6 - with: - version: v1.59 - working-directory: backend - args: --enable-only=gofmt,gofumpt + - name: Lint Go backend + uses: golangci/golangci-lint-action@v6 + with: + version: v1.59 + working-directory: backend + args: --enable-only=gofmt,gofumpt - - name: Analyze Flutter userfront - run: | - cd userfront - flutter analyze --no-fatal-warnings --no-fatal-infos + - name: Analyze Flutter userfront + run: | + cd userfront + flutter analyze --no-fatal-warnings --no-fatal-infos - backend-tests: - needs: lint - if: ${{ inputs.run_backend_tests == true }} - runs-on: ubuntu-latest - services: - redis: - image: redis:7-alpine - options: > - --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 - clickhouse: - image: clickhouse/clickhouse-server:24.6 - options: > - --health-cmd "wget -qO- 'http://localhost:8123/ping'" --health-interval 10s --health-timeout 5s --health-retries 5 + backend-tests: + needs: lint + if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_backend_tests == true }} + runs-on: ubuntu-latest + services: + redis: + image: redis:7-alpine + options: > + --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5 + clickhouse: + image: clickhouse/clickhouse-server:24.6 + options: > + --health-cmd "wget -qO- 'http://localhost:8123/ping'" --health-interval 10s --health-timeout 5s --health-retries 5 - env: - REDIS_ADDR: redis:6379 - CLICKHOUSE_HOST: clickhouse - CLICKHOUSE_PORT_NATIVE: 9000 + env: + REDIS_ADDR: redis:6379 + CLICKHOUSE_HOST: clickhouse + CLICKHOUSE_PORT_NATIVE: 9000 - steps: - - name: Checkout code - uses: actions/checkout@v4 + steps: + - name: Checkout code + uses: actions/checkout@v4 - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: "1.25" - cache-dependency-path: backend/go.sum + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.25" + cache-dependency-path: backend/go.sum - - name: Run backend tests - run: | - cd backend - go test -v ./... + - name: Run backend tests + run: | + cd backend + go test -v ./... - userfront-tests: - needs: lint - if: ${{ inputs.run_userfront_tests == true }} - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 + userfront-tests: + needs: lint + if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_userfront_tests == true }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 - - name: Setup Flutter - uses: subosito/flutter-action@v2 - with: - channel: "stable" - cache: true + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: "stable" + cache: true - - name: Run userfront tests - run: | - cd userfront - if [ -d test ]; then - flutter test - else - echo "No userfront tests: skipping (test/ directory not found)." - fi + - name: Run userfront tests + run: | + cd userfront + if [ -d test ]; then + flutter test + flutter test --platform chrome test/locale_storage_web_test.dart + else + echo "No userfront tests: skipping (test/ directory not found)." + fi diff --git a/README.md b/README.md index 7a870483..5268d366 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ flowchart - userfront가 바라보는 backend ### 2. UserFront(Flutter Web/App) -- **Framework**: Flutter 3.32+ +- **Framework**: Flutter 3.38.0+ - **Key Packages**: `flutter_riverpod`, `go_router` - **Features**: - 탭 기반 로그인 UI (비밀번호 기반 / 링크 기반 / QR 기반 등) @@ -147,7 +147,7 @@ Kratos가 사용자 SoT이며 Hydra는 순수 OIDC 토큰 엔진입니다. 비 ### 사전 요구사항 (Prerequisites) - Docker & Docker Compose -- Flutter SDK (로컬 개발용) +- Flutter SDK (로컬 개발용, 3.38.0+) - Go (로컬 백엔드 개발용) ### 환경 설정 (Environment Setup) diff --git a/README_en.md b/README_en.md index 8d54e892..5ba1130b 100644 --- a/README_en.md +++ b/README_en.md @@ -6,7 +6,7 @@ It leverages **Descope** for secure, passwordless authentication (Enchanted Link ## 🏗 Architecture ### 1. Frontend (Flutter Web) -- **Framework**: Flutter 3.32+ +- **Framework**: Flutter 3.38.0+ - **Organization**: `kr.co.baroncs` - **Key Packages**: `descope`, `flutter_riverpod`, `go_router` - **Features**: @@ -32,7 +32,7 @@ It leverages **Descope** for secure, passwordless authentication (Enchanted Link ### Prerequisites - Docker & Docker Compose -- Flutter SDK (for local development) +- Flutter SDK (for local development, 3.38.0+) - Go (for local backend development) ### Environment Setup diff --git a/adminfront/src/components/common/LanguageSelector.tsx b/adminfront/src/components/common/LanguageSelector.tsx new file mode 100644 index 00000000..fe77df72 --- /dev/null +++ b/adminfront/src/components/common/LanguageSelector.tsx @@ -0,0 +1,57 @@ +import { useState } from "react"; +import { t } from "../../lib/i18n"; + +const LOCALE_STORAGE_KEY = "locale"; +const SUPPORTED_LOCALES = ["ko", "en"] as const; + +type Locale = (typeof SUPPORTED_LOCALES)[number]; + +function resolveLocale(): Locale { + if (typeof window === "undefined") { + return "ko"; + } + + const stored = window.localStorage.getItem(LOCALE_STORAGE_KEY); + if (stored === "ko" || stored === "en") { + return stored; + } + + const pathLocale = window.location.pathname.split("/")[1]; + if (pathLocale === "ko" || pathLocale === "en") { + return pathLocale; + } + + const browserLang = window.navigator.language.toLowerCase(); + return browserLang.startsWith("ko") ? "ko" : "en"; +} + +function LanguageSelector() { + const [locale, setLocale] = useState(resolveLocale()); + + const handleChange = (next: Locale) => { + if (next === locale) { + return; + } + window.localStorage.setItem(LOCALE_STORAGE_KEY, next); + setLocale(next); + window.location.reload(); + }; + + return ( + + ); +} + +export default LanguageSelector; diff --git a/adminfront/src/components/layout/AppLayout.tsx b/adminfront/src/components/layout/AppLayout.tsx index 1894847b..ecbc0385 100644 --- a/adminfront/src/components/layout/AppLayout.tsx +++ b/adminfront/src/components/layout/AppLayout.tsx @@ -16,6 +16,7 @@ import { import { useEffect, useState } from "react"; import { NavLink, Outlet, useNavigate } from "react-router-dom"; import { t } from "../../lib/i18n"; +import LanguageSelector from "../common/LanguageSelector"; import RoleSwitcher from "./RoleSwitcher"; const navItems = [ @@ -170,6 +171,7 @@ function AppLayout() {
+
+