使用虚拟语言文件夹重写 htaccess URL 将页面名称加倍为 URL 中的文件夹

htaccess url rewrite with virtual language folders doubles page name as folder in URL

提问人:Chris Aelbrecht 提问时间:3/30/2023 更新时间:4/1/2023 访问量:81

问:

我有一个有 3 页的小型 PHP 网站。页面内容动态翻译成荷兰语或英语(我从URL中获取语言)

\index.php
\page-one.php
\page-two.php

我想实现以下 URL

https://www.example.com/ => https://www.example.com/en/ or nl/ depending browser language
https://www.example.com/en/ => \index.php
https://www.example.com/en/page-one/ => \page-one.php
https://www.example.com/en/page-two/ => \page-two.php
https://www.example.com/nl/ => \index.php
https://www.example.com/nl/page-one/ => \page-one.php
https://www.example.com/nl/page-two/ => \page-two.php

它在我的 PC 上使用 WAMP 本地工作,具有以下 htaccess

RewriteEngine On
RewriteBase /

RewriteCond %{REQUEST_URI} !(/$|\.) 
RewriteRule (.*) %{REQUEST_URI}/ [R=301,L] 

RewriteCond %{HTTP:Accept-Language} ^nl
RewriteCond %{THE_REQUEST} \ /+(?!(en|nl)/).*
RewriteRule ^(.*)$ /nl/$1 [L,R]
RewriteRule ^nl/(.*)$ /$1 [L]

RewriteCond %{THE_REQUEST} \ /+(?!(en|nl)/).*
RewriteRule ^(.*)$ /en/$1 [L,R]
RewriteRule ^en/(.*)$ /$1 [L]

但是,当我在共享虚拟主机(在 OVH)上发布它时,带有页面名称的子文件夹指向索引文件

OK https://www.example.com/ => https://www.example.com/en/ or nl/
OK https://www.example.com/en/ => \index.php
NOK https://www.example.com/en/page-one/ => \index.php
NOK https://www.example.com/en/page-two/ => \index.php
same for the /nl/

页面仅显示如下

https://www.example.com/en/page-one/page-one/ => \page-one.php
https://www.example.com/en/page-two/page-two/ => \page-two.php

但也有这些URL的作品,这不应该是这样的

https://www.example.com/en/page-one/page-two/ => \page-two.php
https://www.example.com/en/page-two/page-one/ => \page-one.php

似乎它运行了两次 htaccess 的第 10 行和第 14 行。

我该如何解决这个问题?

正则表达式 apache .htaccess mod-rewrite

评论

0赞 MrWhite 3/30/2023
“页面仅显示如下” - 在(实时)共享主机上?和/或本地?你如何管理你的静态资产(JS、CSS、图像等)?这些是否与适当的语言前缀相关联?但是它们被重写以删除它?您有哪些文件系统目录?https://www.example.com/en/page-one/page-one/ => \page-one.php

答:

1赞 MrWhite 3/30/2023 #1

这看起来像是与(mod_negotiation的一部分)发生冲突。这将解释它如何在本地工作,以及实时服务器上的行为差异(我怀疑 MultiView 未启用)。(虽然它似乎没有解释在实时服务器上似乎是如何“工作”的?但是,这将在启用 MultiView 的情况下在本地工作。这同样适用于 - 更多内容见下文。MultiViews/en/page-one/page-one//en/page-one/page-two/

在您的 mod_rewrite 指令中,您不会在任何时候附加扩展,因此它们本身不可能工作(除非您直接请求 - 使用扩展)。因此,看起来您正在依赖 MultiView(它有效地附加了文件扩展名)。.phppage-one.php.php

但也有这些URL的作品,这不应该是这样的

https://www.example.com/en/page-one/page-two/ => \page-two.php
https://www.example.com/en/page-two/page-one/ => \page-one.php

正是 MultiView 允许这样的东西“工作”。虽然我希望情况正好相反。即。 会服务,而不是像你建议的那样?/en/page-one/page-two//page-one.phppage-two.php

这里发生的情况是,您的 mod_rewrite 规则在内部重写了对 的请求。然后,MultiView 向 ( 简称为 PATH-INFO) 发起一个内部子请求,并为其提供服务。/en/page-one/page-two//page-one/page-two//page-one.php/page-two//page-two//page-one.php


您需要确保禁用 MultiView。然后在适当的情况下手动追加扩展。但是,您还没有说明您是如何管理静态资产(JS、CSS、图像等)的?这些是否应该受到相同的重定向/重写?这些语言也是特定的吗?.php

我假设您直接链接到静态资产,因此这些资产不应受到 URL 重写的影响。

请尝试如下操作:

# Ensure that MultiViews is disabled
Options -MultiViews

DirectoryIndex index.php

RewriteEngine On

# Abort early if request has already been rewritten
RewriteCond %{ENV:REDIRECT_STATUS} .
RewriteRule ^ - [L]

# Abort early if request maps to a file OR directory (except root)
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule . - [L]

# Append trailing slash to non-assets
RewriteCond %{REQUEST_URI} !(/$|\.) 
RewriteRule . %{REQUEST_URI}/ [R=301,L]

# Prefix request with language code if omitted (302 - temporary)
# (Defaults to "en" if not "nl" or omitted)
RewriteCond %{HTTP:Accept-Language}@en (?:^|@)(nl|en)
RewriteRule !^(en|nl)/ /%1%{REQUEST_URI} [R=302,L]

# Rewrite to remove language prefix and append ".php" extension if file exists
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(?:en|nl)/([^/]+)/$ $1.php [L]

# Otherwise, rewrite to remove the language prefix (handles the DirectoryIndex)
RewriteRule ^(?:en|nl)/(.*) $1 [L]

原始规则块中的指令不是必需的(这里也不是必需的)。RewriteBase

无需检查,因为当请求已经在内部重写时,我们会提前中止(通过检查环境变量 - 该变量在初始请求中为空,并在第一次成功重写后设置为 HTTP 状态)。THE_REQUESTREDIRECT_STATUS

如果在 Apache 2.4 上,那么您可以在最后两条规则(均重写)上使用标志而不是 ,并删除第一个检查环境变量的“提前中止”规则。该标志停止所有处理,因此不会发生“循环”。ENDLREDIRECT_STATUSEND

进一步的改进...如果直接请求 ,或(即任何带有扩展名的内容),请考虑重定向请求。index.phppage-one.phppage-two.php.php

评论

0赞 Patrick Janser 3/30/2023
默认语言指向英语的好主意!我喜欢第二个 rewritecond 解决方案!
1赞 MrWhite 3/31/2023
@PatrickJanser谢谢,尽管我实际上已经删除了第二个(默认语言),因为它可以在一个条件下完成(但这实际上只是个人喜好)。RewriteCond
0赞 MrWhite 3/31/2023
@ChrisAelbrecht 我还更新了我的答案以解决几个错误......#1在附加扩展名时,我忽略了考虑(强制的)尾部斜杠。#2还忽略了删除对根请求的语言前缀(为了服务) - 我添加了一个额外的规则来涵盖这一点。还增加了一些解释。.phpindex.php
0赞 Chris Aelbrecht 3/31/2023
@MrWhite感谢您的详细回复。它几乎起作用了。我唯一的问题是 www.example.com(有或没有尾部斜杠)不会重定向到 www.example.com/en/(或 nl)。我确实通过将我以前的解决方案与您的解决方案相结合来管理自己,这也解决了所有其他问题。我正在将我的答案粘贴到单独的中。
1赞 Patrick Janser 3/31/2023
最好有一个 RewriteCond 来检测语言并默认为 .我只是在考虑那些拥有多种语言的人(通常在瑞士,我们会说法语、德语、意大利语,并且通常也会英语)。所以匹配可能是一个问题,因为我通常会遇到.这意味着,即使不会说英语,也会说但不将其作为首选语言的人被重定向到。en^accept-language: fr-CH,fr;q=0.9,en-GB;q=0.8,en;q=0.7,de-CH;q=0.6,de;q=0.5nlen
0赞 Chris Aelbrecht 3/31/2023 #2

根据@MrWhite他的回复,以下 .htaccess 会执行预期的操作

Options -MultiViews

DirectoryIndex index.php

RewriteEngine On

# Append trailing slash to non-assets
RewriteCond %{REQUEST_URI} !(/$|\.)
RewriteRule . %{REQUEST_URI}/ [R=301,L]

RewriteCond %{HTTP:Accept-Language} ^nl
RewriteCond %{THE_REQUEST} \ /+(?!(en|nl)/).*
RewriteRule ^(.*)$ /nl/$1 [L,R=302]

RewriteCond %{THE_REQUEST} \ /+(?!(en|nl)/).*
RewriteRule ^(.*)$ /en/$1 [L,R=302]

# Abort early if request has already been rewritten OR maps to a file OR directory
RewriteCond %{ENV:REDIRECT_STATUS} . [OR]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]

# Rewrite to remove language prefix and append ".php" extension if file exists
RewriteCond %{DOCUMENT_ROOT}/$1.php -f
RewriteRule ^(?:en|nl)/([^/]+)/$ $1.php [L]

# Otherwise, rewrite to remove the language prefix (handles the DirectoryIndex)
RewriteRule ^(?:en|nl)/(.*) $1 [L]

评论

0赞 MrWhite 3/31/2023
我的答案中的两行语言代码重定向仍然可以。“提前中止”规则是错误的,因为它还捕获了对根目录的请求(因此跳过了后面以语言代码为前缀的规则)。我已经更新了我的答案来解决这个问题。但是,这些规则现在的顺序是错误的。“提前中止”规则应该是第一位的,以防止静态资产被不必要地重定向。(尽管如果您引用所有带有语言代码前缀的静态资产可能无关紧要?
0赞 Chris Aelbrecht 3/31/2023
我用前导斜杠引用所有静态资产,它会自动重定向到 /en 或 /nl...
0赞 MrWhite 4/1/2023
“which automatically redirects to /en or /nl...” - 静态资产本身不应该被重定向?