diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..c7091c4 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,127 @@ +name: Deploy ASP.NET MVC to OCI + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + +jobs: + build-and-deploy: + runs-on: localACDC + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Cache Docker layers + uses: actions/cache@v3 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- + + - name: Debug - List files + run: | + echo "=== Arquivos na raiz ===" + ls -la + echo "=== Procurando Dockerfile ===" + find . -name "Dockerfile" -o -name "dockerfile" -type f + echo "=== Estrutura do projeto ===" + tree -L 3 || find . -type d -name "convertit" + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: linux/arm64 + push: true + tags: | + registry.redecarneir.us/convertit:latest + registry.redecarneir.us/convertit:${{ github.sha }} + cache-from: | + type=local,src=/tmp/.buildx-cache + type=registry,ref=registry.redecarneir.us/convertit:cache + cache-to: | + type=local,dest=/tmp/.buildx-cache-new,mode=max + type=registry,ref=registry.redecarneir.us/convertit:cache,mode=max + build-args: | + BUILDKIT_INLINE_CACHE=1 + + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + + - name: Deploy to OCI Server + uses: appleboy/ssh-action@v1.0.3 + with: + host: 129.146.116.218 + username: ${{ secrets.SSH_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: 22 + timeout: 60s + script: | + # Para o container anterior da aplicação (se existir) + docker stop convertit || true + docker rm convertit || true + + # Remove imagem antiga + docker rmi registry.redecarneir.us/convertit:latest || true + + # Puxa nova imagem + docker pull registry.redecarneir.us/convertit:latest + + # Executa o novo container na porta 80 + docker run -d \ + --name convertit \ + --restart unless-stopped \ + --add-host="k3ss1:172.17.0.1" \ + --add-host="k3sw2:172.17.0.1" \ + -p 80:8080 \ + --memory=2g \ + --cpus=1.5 \ + --health-cmd="curl -f http://localhost:8080/health || exit 1" \ + --health-interval=30s \ + --health-timeout=10s \ + --health-retries=3 \ + --health-start-period=60s \ + -e ASPNETCORE_ENVIRONMENT=Production \ + -e ASPNETCORE_URLS="http://+:8080" \ + -e DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \ + -e DOTNET_USE_POLLING_FILE_WATCHER=true \ + -e DOTNET_EnableDiagnostics=0 \ + -e DOTNET_RUNNING_IN_CONTAINER=true \ + registry.redecarneir.us/convertit:latest + + # Limpa imagens não utilizadas + docker image prune -f + + # Verifica se o container está rodando + docker ps | grep convertit + + # Testa se a aplicação está respondendo na porta 80 + sleep 10 + curl -f http://localhost:80 || echo "Aplicação pode estar inicializando..." + + - name: Verify deployment + uses: appleboy/ssh-action@v1.0.3 + with: + host: 129.146.116.218 + username: ${{ secrets.SSH_USERNAME }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: 22 + script: | + echo "=== Status dos containers ===" + docker ps -a | grep convertit + + echo "=== Logs da aplicação (últimas 20 linhas) ===" + docker logs convertit --tail 20 + + echo "=== Teste de conectividade ===" + curl -I http://localhost:80 || echo "Aplicação ainda não está acessível" \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8f9238f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,89 @@ +# Claude Context - Convert-It Online + +## Projeto Overview +Convert-It Online é uma aplicação web ASP.NET Core que oferece ferramentas gratuitas de conversão de arquivos com suporte a múltiplos idiomas. + +## URLs Amigáveis ao SEO - SISTEMA IMPLEMENTADO + +### Estrutura de URLs Traduzidas +**IMPORTANTE**: O projeto agora possui URLs totalmente traduzidas para melhor SEO. + +#### URLs por Idioma: + +**Português (pt-BR):** +- Ferramentas de Texto: `/pt-BR/ferramentas-de-texto/conversor-de-maiusculas-minusculas` +- Conversores de Imagem: `/pt-BR/conversores-de-imagem/jpg-para-webp` + +**Espanhol (es-MX, es-CL, es-PY):** +- Herramientas de Texto: `/es-MX/herramientas-de-texto/conversor-de-mayusculas-minusculas` +- Convertidores de Imagen: `/es-MX/convertidores-de-imagen/jpg-a-webp` + +### Arquitetura do Sistema de URLs + +1. **UrlTranslationService**: Mapeia URLs traduzidas para controllers originais +2. **RouteConstraints personalizados**: + - `LocalizedAreaRouteConstraint` + - `LocalizedControllerRouteConstraint` +3. **Resources (.resx)**: Contêm as traduções das URLs em `UrlTextTools`, `UrlImageConverters`, etc. + +## Estrutura de Localização + +### Idiomas Suportados +- `pt-BR` (Português Brasil) - idioma padrão +- `es-MX` (Espanhol México) +- `es-CL` (Espanhol Chile) +- `es-PY` (Espanhol Paraguai) + +### Arquivos de Resources +- `SharedResource.{culture}.resx` - Resources compartilhados incluindo traduções de URL +- `Areas/*/Views/*/Index.{culture}.resx` - Resources específicos de views +- `Views/Home/*.{culture}.resx` - Resources das páginas principais + +## Areas e Controllers + +### TextTools +- **Controller**: `CaseConverterController` +- **Funcionalidade**: Conversão de texto (maiúsculas, minúsculas, primeira maiúscula) +- **URL PT**: `/pt-BR/ferramentas-de-texto/conversor-de-maiusculas-minusculas` +- **URL ES**: `/es-MX/herramientas-de-texto/conversor-de-mayusculas-minusculas` + +### ImageConverters +- **Controller**: `JpgToWebpController` +- **Funcionalidade**: Conversão de JPG para WebP +- **URL PT**: `/pt-BR/conversores-de-imagem/jpg-para-webp` +- **URL ES**: `/es-MX/convertidores-de-imagen/jpg-a-webp` + +## Desenvolvimento Guidelines + +### Adicionando Novos Conversores +1. **Controller**: Criar em `Areas/{AreaName}/Controllers/` +2. **URL Translation**: Adicionar mapeamento em `UrlTranslationService` +3. **Resources**: Adicionar traduções em todos os arquivos `.resx` relevantes +4. **Testing**: Verificar URLs traduzidas em todos os idiomas + +### Manutenção URLs SEO +- ✅ SEMPRE verificar se novas funcionalidades seguem padrão de URL traduzida +- ✅ Manter compatibilidade com URLs antigas (fallback implementado) +- ✅ Validar traduções em todos os idiomas suportados +- ✅ Usar nomes descritivos e friendly nas URLs traduzidas + +### Padrões de Nomenclatura +- URLs sempre em minúsculas +- Usar hífens (-) para separar palavras +- Manter consistência entre idiomas +- Refletir a funcionalidade real da ferramenta + +## Comandos Úteis +- Build: `dotnet build` +- Run: `dotnet run` +- Test local: navegar para `https://localhost:59345/pt-BR/ferramentas-de-texto/conversor-de-maiusculas-minusculas` + +## Status Atual +✅ Sistema de URLs traduzidas implementado e funcionando +✅ Suporte completo para 4 idiomas +✅ RouteConstraints personalizados implementados +✅ Fallback para URLs antigas mantido +🔄 Aguardando testes finais + +--- +*Mantenha este arquivo atualizado quando adicionar novos conversores ou idiomas.* \ No newline at end of file diff --git a/Convert-It.sln b/Convert-It.sln index 33e1161..af0c721 100644 --- a/Convert-It.sln +++ b/Convert-It.sln @@ -1,10 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.14.36414.22 d17.14 +VisualStudioVersion = 17.14.36414.22 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Convert-It", "Convert-It.csproj", "{D8E3BC66-82F6-0019-7082-DF2342D15CEC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deploy", "deploy", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + .gitea\workflows\deploy.yml = .gitea\workflows\deploy.yml + Dockerfile = Dockerfile + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..82dd465 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +# Dockerfile self-contained para m�xima performance +FROM --platform=linux/arm64 mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM --platform=linux/arm64 mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src + +# Copiar apenas arquivos de projeto primeiro (melhor cache) +COPY ["Convert-It.csproj", "./"] + +# Restore com configura��es otimizadas para ARM64 +RUN dotnet restore "./Convert-It.csproj" \ + --runtime linux-arm64 \ + --no-cache + +# Copiar c�digo fonte +COPY . . +WORKDIR "/src" + +# Build otimizado +RUN dotnet build "./Convert-It.csproj" \ + -c $BUILD_CONFIGURATION \ + -o /app/build \ + --runtime linux-arm64 \ + --no-restore + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release + +# Publish self-contained para eliminar cold start +RUN dotnet publish "./Convert-It.csproj" \ + -c $BUILD_CONFIGURATION \ + -o /app/publish \ + --runtime linux-arm64 \ + --no-restore \ + --self-contained true \ + /p:PublishTrimmed=true \ + /p:PublishSingleFile=false + +FROM mcr.microsoft.com/dotnet/runtime-deps:8.0 AS final +WORKDIR /app +COPY --from=publish /app/publish . + +# Vari�veis de ambiente otimizadas para produ��o +ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +ENV DOTNET_USE_POLLING_FILE_WATCHER=true +ENV ASPNETCORE_ENVIRONMENT=Production +ENV DOTNET_EnableDiagnostics=0 + +# Healthcheck simples +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +ENTRYPOINT ["./Convert-It"] \ No newline at end of file diff --git a/Extensions/HtmlHelperExtensions.cs b/Extensions/HtmlHelperExtensions.cs new file mode 100644 index 0000000..cc3ebe3 --- /dev/null +++ b/Extensions/HtmlHelperExtensions.cs @@ -0,0 +1,41 @@ +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Convert_It_Online.Services; +using System.Globalization; + +namespace Convert_It_Online.Extensions +{ + public static class HtmlHelperExtensions + { + public static string LocalizedUrl(this IHtmlHelper htmlHelper, string area, string controller, string action = "Index") + { + var context = htmlHelper.ViewContext.HttpContext; + var urlTranslationService = context.RequestServices.GetRequiredService(); + + // Get current culture + var cultureFeature = context.Features.Get(); + var culture = cultureFeature?.RequestCulture.UICulture ?? new CultureInfo("pt-BR"); + + // Get localized URL + var localizedUrl = urlTranslationService.GetLocalizedUrl(area, controller, action, culture); + + return localizedUrl; + } + + public static IHtmlContent LocalizedLink(this IHtmlHelper htmlHelper, string linkText, string area, string controller, string action = "Index", object? htmlAttributes = null) + { + var url = htmlHelper.LocalizedUrl(area, controller, action); + + var attributes = ""; + if (htmlAttributes != null) + { + var props = htmlAttributes.GetType().GetProperties(); + var attrList = props.Select(p => $"{p.Name.Replace('_', '-')}=\"{p.GetValue(htmlAttributes)}\""); + attributes = " " + string.Join(" ", attrList); + } + + var html = $"{linkText}"; + return new HtmlString(html); + } + } +} \ No newline at end of file diff --git a/Middleware/UrlTranslationMiddleware.cs b/Middleware/UrlTranslationMiddleware.cs new file mode 100644 index 0000000..ac023b8 --- /dev/null +++ b/Middleware/UrlTranslationMiddleware.cs @@ -0,0 +1,72 @@ +using Convert_It_Online.Services; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Convert_It_Online.Middleware +{ + public class UrlTranslationMiddleware + { + private readonly RequestDelegate _next; + private readonly IUrlTranslationService _urlTranslationService; + private readonly ILogger _logger; + + public UrlTranslationMiddleware(RequestDelegate next, IUrlTranslationService urlTranslationService, ILogger logger) + { + _next = next; + _urlTranslationService = urlTranslationService; + _logger = logger; + } + + public async Task InvokeAsync(HttpContext context) + { + var path = context.Request.Path.Value; + + if (!string.IsNullOrEmpty(path)) + { + _logger.LogInformation($"Processing path: {path}"); + + // Pattern: /{culture}/{translatedArea}/{translatedController}[/{action}] + var match = Regex.Match(path, @"^/([a-z]{2}-[A-Z]{2})/([^/]+)/([^/]+)(?:/([^/]+))?"); + + if (match.Success) + { + var cultureName = match.Groups[1].Value; + var translatedArea = match.Groups[2].Value; + var translatedController = match.Groups[3].Value; + var action = match.Groups[4].Success ? match.Groups[4].Value : "Index"; + + _logger.LogInformation($"Matched: culture={cultureName}, area={translatedArea}, controller={translatedController}, action={action}"); + + try + { + var culture = new CultureInfo(cultureName); + var originalArea = _urlTranslationService.GetOriginalArea(translatedArea, culture); + var originalController = _urlTranslationService.GetOriginalController(translatedController, culture); + + _logger.LogInformation($"Translations: originalArea={originalArea}, originalController={originalController}"); + + if (originalArea != null && originalController != null) + { + // Rewrite the path to use original controller names + var newPath = $"/{cultureName}/{originalArea}/{originalController}"; + if (!string.IsNullOrEmpty(action) && action != "Index") + { + newPath += $"/{action}"; + } + + _logger.LogInformation($"Rewriting path from {path} to {newPath}"); + context.Request.Path = newPath; + } + } + catch (CultureNotFoundException) + { + _logger.LogWarning($"Culture {cultureName} not found"); + // Continue with original path if culture is not supported + } + } + } + + await _next(context); + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index a70b78f..6ebe620 100644 --- a/Program.cs +++ b/Program.cs @@ -1,53 +1,53 @@ using System.Globalization; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization.Routing; +using Microsoft.Extensions.Options; +using Convert_It_Online.Services; +using Convert_It_Online.Middleware; -// 1. Builder e Serviços var builder = WebApplication.CreateBuilder(args); builder.Services.AddLocalization(); +builder.Services.AddSingleton(); + +var supportedCultures = new[] { "pt-BR", "es-MX", "es-CL", "es-PY" }; +builder.Services.Configure(options => +{ + options.DefaultRequestCulture = new RequestCulture("pt-BR"); + options.SupportedCultures = supportedCultures.Select(c => new CultureInfo(c)).ToList(); + options.SupportedUICultures = supportedCultures.Select(c => new CultureInfo(c)).ToList(); +}); + builder.Services.AddControllersWithViews() .AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix); var app = builder.Build(); -// 2. Configuração de Localização (RequestLocalizationOptions) -var supportedCultures = new[] { "pt-BR", "es-MX", "es-CL", "es-PY" }; - -var localizationOptions = new RequestLocalizationOptions -{ - DefaultRequestCulture = new RequestCulture("pt-BR"), - SupportedCultures = supportedCultures.Select(c => new CultureInfo(c)).ToList(), - SupportedUICultures = supportedCultures.Select(c => new CultureInfo(c)).ToList() -}; +var localizationOptions = app.Services.GetRequiredService>().Value; localizationOptions.RequestCultureProviders.Clear(); localizationOptions.RequestCultureProviders.Insert(0, new RouteDataRequestCultureProvider()); -localizationOptions.RequestCultureProviders.Insert(1, new QueryStringRequestCultureProvider()); -localizationOptions.RequestCultureProviders.Insert(2, new AcceptLanguageHeaderRequestCultureProvider()); +localizationOptions.RequestCultureProviders.Insert(1, new AcceptLanguageHeaderRequestCultureProvider()); +localizationOptions.RequestCultureProviders.Insert(2, new QueryStringRequestCultureProvider()); // 3. Pipeline de Middlewares (na ordem correta) if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } -else -{ - app.UseExceptionHandler("/Home/Error"); - app.UseHsts(); -} + +app.UseExceptionHandler("/Home/Error"); +app.UseHsts(); app.UseHttpsRedirection(); app.UseStaticFiles(); +app.UseMiddleware(); app.UseRouting(); - app.UseRequestLocalization(localizationOptions); - app.UseAuthorization(); -// 4. Mapeamento de Rotas (com cultura) app.MapControllerRoute( name: "areaRoute", pattern: "{culture:length(2,5)}/{area:exists}/{controller=Home}/{action=Index}/{id?}"); @@ -61,5 +61,4 @@ app.MapControllerRoute( pattern: "{controller=Home}/{action=Index}/{id?}", defaults: new { culture = "pt-BR" }); -// 5. Execução app.Run(); diff --git a/Routing/LocalizedAreaRouteConstraint.cs b/Routing/LocalizedAreaRouteConstraint.cs new file mode 100644 index 0000000..4672dc2 --- /dev/null +++ b/Routing/LocalizedAreaRouteConstraint.cs @@ -0,0 +1,37 @@ +using Convert_It_Online.Services; +using Microsoft.AspNetCore.Routing; +using System.Globalization; + +namespace Convert_It_Online.Routing +{ + public class LocalizedAreaRouteConstraint : IRouteConstraint + { + private readonly IUrlTranslationService _urlTranslationService; + + public LocalizedAreaRouteConstraint(IUrlTranslationService urlTranslationService) + { + _urlTranslationService = urlTranslationService; + } + + public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, + RouteValueDictionary values, RouteDirection routeDirection) + { + if (values.TryGetValue("translatedArea", out var value) && value is string areaName) + { + if (values.TryGetValue("culture", out var cultureValue) && cultureValue is string cultureName) + { + var culture = new CultureInfo(cultureName); + var originalArea = _urlTranslationService.GetOriginalArea(areaName, culture); + + if (originalArea != null) + { + values["area"] = originalArea; + return true; + } + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Routing/LocalizedControllerRouteConstraint.cs b/Routing/LocalizedControllerRouteConstraint.cs new file mode 100644 index 0000000..317d900 --- /dev/null +++ b/Routing/LocalizedControllerRouteConstraint.cs @@ -0,0 +1,37 @@ +using Convert_It_Online.Services; +using Microsoft.AspNetCore.Routing; +using System.Globalization; + +namespace Convert_It_Online.Routing +{ + public class LocalizedControllerRouteConstraint : IRouteConstraint + { + private readonly IUrlTranslationService _urlTranslationService; + + public LocalizedControllerRouteConstraint(IUrlTranslationService urlTranslationService) + { + _urlTranslationService = urlTranslationService; + } + + public bool Match(HttpContext? httpContext, IRouter? route, string routeKey, + RouteValueDictionary values, RouteDirection routeDirection) + { + if (values.TryGetValue("translatedController", out var value) && value is string controllerName) + { + if (values.TryGetValue("culture", out var cultureValue) && cultureValue is string cultureName) + { + var culture = new CultureInfo(cultureName); + var originalController = _urlTranslationService.GetOriginalController(controllerName, culture); + + if (originalController != null) + { + values["controller"] = originalController; + return true; + } + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/Services/IUrlTranslationService.cs b/Services/IUrlTranslationService.cs new file mode 100644 index 0000000..bdddb0a --- /dev/null +++ b/Services/IUrlTranslationService.cs @@ -0,0 +1,13 @@ +using System.Globalization; + +namespace Convert_It_Online.Services +{ + public interface IUrlTranslationService + { + string TranslateArea(string area, CultureInfo culture); + string TranslateController(string controller, CultureInfo culture); + string? GetOriginalArea(string translatedArea, CultureInfo culture); + string? GetOriginalController(string translatedController, CultureInfo culture); + string GetLocalizedUrl(string area, string controller, string action, CultureInfo culture); + } +} \ No newline at end of file diff --git a/Services/UrlTranslationService.cs b/Services/UrlTranslationService.cs new file mode 100644 index 0000000..6d6522c --- /dev/null +++ b/Services/UrlTranslationService.cs @@ -0,0 +1,144 @@ +using System.Globalization; + +namespace Convert_It_Online.Services +{ + public class UrlTranslationService : IUrlTranslationService + { + private readonly Dictionary> _areaTranslations; + private readonly Dictionary> _controllerTranslations; + private readonly Dictionary> _reverseAreaTranslations; + private readonly Dictionary> _reverseControllerTranslations; + + public UrlTranslationService() + { + _areaTranslations = new Dictionary> + { + ["pt-BR"] = new Dictionary + { + ["TextTools"] = "ferramentas-de-texto", + ["ImageConverters"] = "conversores-de-imagem" + }, + ["es-MX"] = new Dictionary + { + ["TextTools"] = "herramientas-de-texto", + ["ImageConverters"] = "convertidores-de-imagen" + }, + ["es-CL"] = new Dictionary + { + ["TextTools"] = "herramientas-de-texto", + ["ImageConverters"] = "convertidores-de-imagen" + }, + ["es-PY"] = new Dictionary + { + ["TextTools"] = "herramientas-de-texto", + ["ImageConverters"] = "convertidores-de-imagen" + } + }; + + _controllerTranslations = new Dictionary> + { + ["pt-BR"] = new Dictionary + { + ["CaseConverter"] = "conversor-de-maiusculas-minusculas", + ["JpgToWebp"] = "jpg-para-webp" + }, + ["es-MX"] = new Dictionary + { + ["CaseConverter"] = "conversor-de-mayusculas-minusculas", + ["JpgToWebp"] = "jpg-a-webp" + }, + ["es-CL"] = new Dictionary + { + ["CaseConverter"] = "conversor-de-mayusculas-minusculas", + ["JpgToWebp"] = "jpg-a-webp" + }, + ["es-PY"] = new Dictionary + { + ["CaseConverter"] = "conversor-de-mayusculas-minusculas", + ["JpgToWebp"] = "jpg-a-webp" + } + }; + + // Create reverse mappings for lookup + _reverseAreaTranslations = CreateReverseMappings(_areaTranslations); + _reverseControllerTranslations = CreateReverseMappings(_controllerTranslations); + } + + private Dictionary> CreateReverseMappings( + Dictionary> original) + { + var reverse = new Dictionary>(); + + foreach (var culture in original.Keys) + { + reverse[culture] = new Dictionary(); + foreach (var kvp in original[culture]) + { + reverse[culture][kvp.Value] = kvp.Key; + } + } + + return reverse; + } + + public string TranslateArea(string area, CultureInfo culture) + { + var cultureName = culture.Name; + + if (_areaTranslations.TryGetValue(cultureName, out var areaDict) && + areaDict.TryGetValue(area, out var translatedArea)) + { + return translatedArea; + } + + return area.ToLowerInvariant(); + } + + public string TranslateController(string controller, CultureInfo culture) + { + var cultureName = culture.Name; + + if (_controllerTranslations.TryGetValue(cultureName, out var controllerDict) && + controllerDict.TryGetValue(controller, out var translatedController)) + { + return translatedController; + } + + return controller.ToLowerInvariant(); + } + + public string? GetOriginalArea(string translatedArea, CultureInfo culture) + { + var cultureName = culture.Name; + + if (_reverseAreaTranslations.TryGetValue(cultureName, out var areaDict) && + areaDict.TryGetValue(translatedArea, out var originalArea)) + { + return originalArea; + } + + return null; + } + + public string? GetOriginalController(string translatedController, CultureInfo culture) + { + var cultureName = culture.Name; + + if (_reverseControllerTranslations.TryGetValue(cultureName, out var controllerDict) && + controllerDict.TryGetValue(translatedController, out var originalController)) + { + return originalController; + } + + return null; + } + + public string GetLocalizedUrl(string area, string controller, string action, CultureInfo culture) + { + var translatedArea = TranslateArea(area, culture); + var translatedController = TranslateController(controller, culture); + + return $"/{culture.Name}/{translatedArea}/{translatedController}"; + } + } +} \ No newline at end of file diff --git a/SharedResource.es-CL.resx b/SharedResource.es-CL.resx new file mode 100644 index 0000000..410765c --- /dev/null +++ b/SharedResource.es-CL.resx @@ -0,0 +1,122 @@ + + + + + Convertidores Gratuitos Online + + + Convierte textos, imágenes y documentos gratis. Herramientas online bacanes y seguras, sin necesidad de instalar nada. + + + Elige tu Conversor + + + Herramientas de Texto + + + Convierte, formatea y manipula textos súper fácil. + + + Conversores de Imagen + + + Optimiza y convierte imágenes a cualquier formato al tiro. + + + Sobre Convert-It Online + + + Convert-It Online es una plataforma gratuita que ofrece varias herramientas de conversión para que puedas hacer tu pega más fácil. Todas las conversiones son súper seguras, no guardamos tus archivos en nuestros servidores. + + + ¿Por qué es gratis? + + + Creemos que las herramientas útiles deberían estar al alcance de todos. Nuestro sitio se mantiene con publicidad no molesta, así puedes usar todos los conversores sin pagar ni una luca. + + + Seguridad y privacidad + + + Tus archivos se procesan en tu navegador cuando se puede. Para conversiones que necesitan procesamiento en el servidor, los archivos se borran automáticamente después de la conversión. + + + + + Inicio + + + Texto + + + Imagen + + + Conversor de Mayúsculas/Minúsculas + + + JPG a WebP + + + + + © 2025 Convert-It Online. Herramientas gratis de conversión. + + + Sobre nosotros + + + Contacto + + + Términos + + + + + Conversor de Texto + + + Pega tu texto acá + + + MAYÚSCULAS + + + minúsculas + + + Primera mayúscula + + + Resultado + + + + + Conversor JPG a WebP + + + Convierte tus imágenes JPG al formato WebP al tiro y eficiente. WebP te da mejor compresión manteniendo la calidad de la imagen. + + + Selecciona un archivo JPG + + + Convertir a WebP + + + + + herramientas-de-texto + + + convertidores-de-imagen + + + conversor-de-mayusculas-minusculas + + + jpg-a-webp + + \ No newline at end of file diff --git a/SharedResource.es-MX.resx b/SharedResource.es-MX.resx new file mode 100644 index 0000000..3900f11 --- /dev/null +++ b/SharedResource.es-MX.resx @@ -0,0 +1,122 @@ + + + + + Convertidores Gratuitos en Línea + + + Convierte textos, imágenes y documentos gratis. Herramientas en línea rápidas y seguras, sin necesidad de instalar nada. + + + Elige tu Convertidor + + + Herramientas de Texto + + + Convierte, formatea y manipula textos fácilmente. + + + Convertidores de Imagen + + + Optimiza y convierte imágenes a cualquier formato. + + + Acerca de Convert-It Online + + + Convert-It Online es una plataforma gratuita que ofrece diversas herramientas de conversión para facilitar tu trabajo diario. Todas las conversiones se realizan de forma segura, sin almacenar tus archivos en nuestros servidores. + + + ¿Por qué es gratis? + + + Creemos que las herramientas útiles deben ser accesibles para todos. Nuestro sitio se mantiene a través de anuncios no intrusivos, permitiendo que uses todos los convertidores sin costo alguno. + + + Seguridad y privacidad + + + Tus archivos se procesan localmente en tu navegador siempre que sea posible. Para conversiones que requieren procesamiento en el servidor, los archivos se eliminan automáticamente después de la conversión. + + + + + Inicio + + + Texto + + + Imagen + + + Convertidor de Mayúsculas/Minúsculas + + + JPG a WebP + + + + + © 2025 Convert-It Online. Herramientas gratuitas de conversión. + + + Acerca de + + + Contacto + + + Términos + + + + + Convertidor de Texto + + + Escribe tu texto aquí + + + MAYÚSCULAS + + + minúsculas + + + Primera mayúscula + + + Resultado + + + + + Convertidor JPG a WebP + + + Convierte tus imágenes JPG al formato WebP de forma rápida y eficiente. WebP ofrece mejor compresión manteniendo la calidad de la imagen. + + + Selecciona un archivo JPG + + + Convertir a WebP + + + + + herramientas-de-texto + + + convertidores-de-imagen + + + conversor-de-mayusculas-minusculas + + + jpg-a-webp + + \ No newline at end of file diff --git a/SharedResource.es-PY.resx b/SharedResource.es-PY.resx new file mode 100644 index 0000000..307356e --- /dev/null +++ b/SharedResource.es-PY.resx @@ -0,0 +1,122 @@ + + + + + Convertidores Gratuitos en Línea + + + Convierte textos, imágenes y documentos gratis. Herramientas en línea rápidas y seguras, sin necesidad de instalar nada che. + + + Elige tu Convertidor + + + Herramientas de Texto + + + Convierte, formatea y manipula textos facilito. + + + Convertidores de Imagen + + + Optimiza y convierte imágenes a cualquier formato al toque. + + + Acerca de Convert-It Online + + + Convert-It Online es una plataforma gratuita que ofrece diversas herramientas de conversión para facilitar tu trabajo diario che. Todas las conversiones se realizan de forma segura, sin guardar tus archivos en nuestros servidores. + + + ¿Por qué es gratis? + + + Creemos que las herramientas útiles deben estar al alcance de todos. Nuestro sitio se mantiene a través de publicidad no molesta, así podés usar todos los convertidores sin pagar nada che. + + + Seguridad y privacidad + + + Tus archivos se procesan localmente en tu navegador cuando es posible. Para conversiones que necesitan procesamiento en el servidor, los archivos se borran automáticamente después de la conversión. + + + + + Inicio + + + Texto + + + Imagen + + + Convertidor de Mayúsculas/Minúsculas + + + JPG a WebP + + + + + © 2025 Convert-It Online. Herramientas gratuitas de conversión. + + + Acerca de + + + Contacto + + + Términos + + + + + Convertidor de Texto + + + Escribí tu texto acá + + + MAYÚSCULAS + + + minúsculas + + + Primera mayúscula + + + Resultado + + + + + Convertidor JPG a WebP + + + Convertí tus imágenes JPG al formato WebP de forma rápida y eficiente che. WebP ofrece mejor compresión manteniendo la calidad de la imagen. + + + Seleccioná un archivo JPG + + + Convertir a WebP + + + + + herramientas-de-texto + + + convertidores-de-imagen + + + conversor-de-mayusculas-minusculas + + + jpg-a-webp + + \ No newline at end of file diff --git a/SharedResource.pt-BR.resx b/SharedResource.pt-BR.resx index 4a5b3dc..bf03b4b 100644 --- a/SharedResource.pt-BR.resx +++ b/SharedResource.pt-BR.resx @@ -204,6 +204,20 @@ Termos + + + ferramentas-de-texto + + + conversores-de-imagem + + + conversor-de-maiusculas-minusculas + + + jpg-para-webp + + Como posso converter meus arquivos de forma segura? diff --git a/Views/Home/Index.cshtml b/Views/Home/Index.cshtml index b269854..160d02a 100644 --- a/Views/Home/Index.cshtml +++ b/Views/Home/Index.cshtml @@ -17,10 +17,7 @@
@foreach (var tool in Model) { -
diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 5966486..a24987e 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -37,7 +37,7 @@ @ViewBag.TextMenuTitle @@ -47,7 +47,7 @@ @ViewBag.ImageMenuTitle diff --git a/Views/_ViewImports.cshtml b/Views/_ViewImports.cshtml index 78d2e97..c5060df 100644 --- a/Views/_ViewImports.cshtml +++ b/Views/_ViewImports.cshtml @@ -1,4 +1,5 @@ @using Convert_It_Online +@using Convert_It_Online.Extensions @using Microsoft.AspNetCore.Mvc.Localization @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers