Da direita para a esquerda. Como ativar a interface do site em RTL

imagem


Recentemente, traduzimos a versão online do 2GIS para o árabe e, em um artigo anterior , conversamos sobre a teoria necessária para isso - o que é dir="rtl" , pelas regras em que um texto de foco misto é exibido e como se controlar.


É hora de começar a praticar - girar toda a interface da direita para a esquerda com o mínimo esforço, para que mesmo um árabe de verdade não sinta o problema.


Neste artigo, mostrarei como prototipar rapidamente o que fazer com a montagem CSS e quais muletas decompor no JS, um pouco sobre os recursos de tradução e localização, um lembrete das propriedades lógicas do CSS e o tópico RTL no CSS-in-JS.


Estilos de inversão


Quando apliquei o atributo dir = "rtl" à tag, apenas a ordem implícita dos elementos foi alterada - por exemplo, a ordem das células da tabela ou dos elementos flexíveis. Nada aconteceu com os valores especificados explicitamente nos estilos.

Pegue os estilos de algumas notificações localizadas no canto inferior direito:


 .tooltip { position: 'absolute'; bottom: 10px; right: 10px; } 

dir="rtl" não afetará esses estilos de forma alguma - na versão RTL, a dica de ferramenta também estará à direita, embora seja esperada à esquerda.


O que fazer Você precisa substituir right: 10px pela left: 10px . E assim com todos os outros estilos. Posicionamento absoluto, margem / preenchimento, alinhamento de texto - tudo precisa ser girado na direção oposta à versão em árabe.


Protótipo rápido


Para começar, você pode, sem hesitar, alterar todas as ocorrências da esquerda para a direita e fazer um pouco de mágica com valores abreviados:


  • esquerda: 0 → direita: 0
  • padding-left: 4px → padding-right: 4px
  • margem: 0 16px 0 0 → margem: 0 0 0 16px

O plugin postcss-rtl é adequado para isso. Conveniente - você só precisa colocá-lo na lista de todos os plugins do projeto postcss. Ele substitui todas as regras direcionadas por espelhadas e as agrupa em [dir="rtl"] . Por exemplo:


 /* input */ .foo { color: red; margin-left: 16px; } /* output */ [dir] .foo { color: red; } [dir="ltr"] .foo { margin-left: 16px; } [dir="rtl"] .foo { margin-right: 16px; } 

Depois disso, você só precisa definir dir="rtl" e somente as regras necessárias são aplicadas automaticamente. Tudo funciona e parece que quase tudo está pronto para produção, mas esta solução é adequada apenas para um protótipo rápido:


  • a especificidade de cada regra aumenta . Isso não será necessariamente um problema, mas eu gostaria de evitá-lo;
  • essas manipulações dão origem a erros . Por exemplo, a ordem das propriedades pode quebrar ;
  • o tamanho do arquivo css está aumentando notavelmente . [dir] adicionado a cada seletor, cada propriedade direcionada é duplicada. No nosso caso, o tamanho aumentou em um projeto em 21%, em outro - em 35%:

tamanho original (gzip)tamanho bidirecional (gzip)inchado em
2gis.ru272,3 kB329,7 kB21%
m.2gis.ru24,5 kB33,2 kB35%
habr.com33,1 kB41,4 kB25%

Existe uma opção melhor?


É necessário coletar estilos para LTR e RTL separadamente. Então não será necessário tocar nos seletores e o tamanho do css dificilmente mudará.


Para isso, eu escolhi:


  1. RTLCSS - Esta biblioteca está sob o capô do postcss-rtl.
  2. O webpack-rtl-plugin é uma solução chave na mão para estilos criados por meio do ExtractTextPlugin. O mesmo RTLCSS sob o capô.

E ele começou a coletar RTL e LTR em diferentes arquivos - styles.css e styles.rtl.css . O único ponto negativo de montagem em arquivos diferentes é que você não pode substituir o dir on the fly sem primeiro fazer o download do arquivo desejado.


O RTLCSS permite usar diretivas para controlar o processamento de regras específicas, por exemplo:


 .foo { /*rtl:ignore*/ right: 0; } .bar { font-size:16px/*rtl:14px*/; } 

Que outras soluções existem?


Todas as soluções existentes quase não são diferentes do RTLCSS.


  • CSS-flip do Twitter;
  • cssjanus da Wikimedia;
  • Sim, e o postcss-rtl suporta o parâmetro onlyDirection , com o qual você pode coletar estilos para apenas uma direção, mas o tamanho ainda aumenta - por exemplo, para 2GIS móvel, é de 18% em vez de 35% (24,5 kB → 29 kB).

Quando são necessárias diretrizes?


Quando os estilos não devem depender da orientação


Por exemplo, o ângulo de rotação da seta indicando a direção do vento:


imagem
 .arrow._nw { /*rtl:ignore*/ transform: rotate(135deg); } 



Ou uma diminuição no número de telefone - os números são sempre escritos da esquerda para a direita, o que significa que o gradiente deve estar sempre à direita:


imagem


imagem


Quando centralizar o ícone


Este é um caso especial do parágrafo anterior. Se centralizarmos o ícone assimétrico através do recuo / posicionamento, mudaremos seu bloco para o lado e, se ele refletir o deslocamento, o ícone "moverá" para o outro lado:


imagem


É melhor centralizar o ícone no próprio svg nessas situações:



Quando você precisa isolar um widget inteiro que não deve responder ao RTL


No nosso caso, este é um mapa. Embrulhamos todos os seus estilos ao montar diretivas de bloco: /*rtl:begin:ignore*/ ... /*rtl:end:ignore*/ .


Existe uma opção ainda melhor?


A solução com a reversão das regras funciona bem, mas surge a pergunta - é uma muleta? A dependência de estilos em relação à direção é uma tarefa natural da web moderna e sua relevância cresce a cada ano. Isso deveria ter se refletido nos padrões e abordagens modernos. E encontrado!


Propriedades lógicas


Para adaptar o layout para diferentes orientações, existe um padrão para propriedades lógicas no css . Diz respeito não apenas às direções da esquerda para a direita e da direita para a esquerda, mas também à direção de cima para baixo, mas não a consideraremos.


Já usamos algo semelhante em flexões e grades - por exemplo, flex-start , flex-end , grid-row-start , grid-column-end desatados da esquerda / direita.


Em vez dos conceitos left , right , top e bottom propõe-se usar inline-start , inline-end , block-start e block-end . Em vez de width e height - inline-size block-size . E, em vez de atalhos, abcd - logical adcb (atalhos lógicos vão no sentido anti-horário). Além disso, para emparelhar as falhas existentes, novas versões emparelhadas são exibidas - padding-block , margin-inline , border-color-inline etc.


  • left: 0 → inset-inline-start: 0
  • padding-left: 4px → padding-inline-start: 4px
  • margin: 0 16px 0 0 → margin: logical 0 0 0 16px
  • padding-top: 8px; padding-bottom: 16px → padding-block: 8px 16px
  • margin-left: 4px; margin-right: 8px → margin-inline: 4px 8px
  • text-align: right → text-align: end

E a taquigrafia aguardada para posicionamento aparece:


  • left: 4px; right: 8px → inset-inline: 4px 8px
  • top: 8px; bottom: 16px → inset-block: 8px 16px
  • top: 0; right: 2px; bottom: 2px; left: 0 → inset: logical 0 0 2px 2px

Isso está disponível no Firefox sem sinalizadores e nos navegadores webkit baseados em sinalizadores.


Prós - a solução é nativa, funcionará sem montagem / plugins, se os navegadores necessários forem suportados. Não há necessidade de diretivas - basta escrever à left vez de inline-start , quando você quer dizer a "esquerda" física.


Os contras são dos profissionais - sem plug-ins, o código na maioria dos navegadores é inválido, você precisa fazer muito trabalho para traduzir um grande projeto existente.


Como se conectar?


A maneira mais fácil é a lógica postcss . Sem o parâmetro dir , ele coleta estilos para ambas as direções da mesma maneira que postcss-rtl, com o parâmetro dir especificado, apenas para a direção especificada:


 .banner { color: #222222; inset: logical 0 5px 10px; padding-inline: 20px 40px; resize: block; transition: color 200ms; } /* becomes */ .banner { color: #222222; top: 0; left: 5px; bottom: 10px; right: 5px; &:dir(ltr) { padding-left: 20px; padding-right: 40px; } &:dir(rtl) { padding-right: 20px; padding-left: 40px; } resize: vertical; transition: color 200ms; } /* or, when used with { dir: 'ltr' } */ .banner { color: #222222; top: 0; left: 5px; bottom: 10px; right: 5px; padding-left: 20px; padding-right: 40px; resize: vertical; transition: color 200ms; } 

Como convencer uma equipe a começar a escrever offset-inline-start em vez de esquerda?


De jeito nenhum. Mas decidimos em nosso projeto simplificá-lo - escreva start: 0 vez de offset-inline-start: 0 , assim que todos se acostumarem, começarei a impor uma entrada válida :)


RTL + CSS-in-JS = ️️ <3


CSS-in-JS não precisa ser montado previamente. Isso significa que, em tempo de execução, é possível determinar a direção dos componentes e escolher quais virar e quais não. Útil se você precisar inserir algum widget que não suporte RTL.


Em geral, a tarefa é transformar objetos do tipo { paddingInlineStart: '4px' } (ou { paddingLeft: '4px' } , se não for possível alternar para propriedades lógicas) em objetos do tipo { paddingRight: '4px' } :


  1. Armar com bidi-css-js ou rtl-css-js . Eles fornecem uma função que pega um objeto de estilo e retorna transformado para a orientação desejada.
  2. ???
  3. LUCRO!

Exemplo de Reação


Embrulhe cada componente com estilo em um HOC que aceite estilos:


 export default withStyles(styles)(Button); 

Ele toma a direção do componente do contexto e seleciona os estilos finais:


 function withStyles(styles) { const { ltrStyles, rtlStyles } = bidi(styles); return function WithStyles(WrappedComponent) { ... render() { return <WrappedComponent {...this.props} styles={this.context.dir === 'rtl' ? rtlStyles : ltrStyles} />; }; }; ... }; } 

E o provedor lança o foco no contexto:


 <DirectionProvider dir="rtl"> ... <Button /> ... 

O Airbnb usa uma abordagem semelhante: https://github.com/airbnb/react-with-styles-interface-aphrodite#built-in-rtl-support , se o afrodito já estiver sendo usado no projeto, você poderá usar esta solução pronta.


Para o JSS, ainda é mais simples - você só precisa ativar o jss-rtl :


 jss.use(rtl()); 

componentes estilizados


 const Button = styled.button` background: #222; margin-left: 12px; `; 

E se trabalharmos com seqüências de caracteres de modelo, e não com objetos? Tudo é complicado, mas existe uma solução - calcule o nome da propriedade a partir da direção especificada em props :


 const marginStart = props => props.theme.dir === "rtl" ? "margin-left" : "margin-right"; const Button = styled.button` background: #222; ${marginStart}: 12px; `; 

Mas parece mais fácil alternar de linhas para objetos; os componentes estilizados conseguiram fazer isso desde a versão 3.3.0 .


Recursos de tradução e localização


Nós descobrimos a parte técnica. Eles isolaram o conteúdo de uma orientação indeterminada, espelharam os estilos, colocaram exceções nos lugares certos, traduziram os textos para o árabe. Tudo parece estar pronto - ao alternar o idioma, toda a interface aparece do outro lado da tela, não há layout e tudo parece melhor do que em qualquer site em árabe.


Mostramos o verdadeiro árabe e ...


Acontece que nem todo falante de árabe sabe o que é o Twitter. Isso se aplica a quase todas as palavras em inglês. Nesse caso, existe uma transliteração para o árabe: "تويتر".


Acontece que em árabe há vírgulas e o fato de termos concatenado em todo lugar no código por meio de ",", em árabe, precisamos concatenado por "،".


Acontece que em alguns países muçulmanos o calendário oficial é islâmico. É lunar e a fórmula de tradução usual é indispensável.


Acontece que em Dubai não há temperatura negativa e o sinal de mais na previsão de +40 não faz sentido.


Não apenas estilos escolhidos e espelhados


Se fizermos dir="auto" no elemento de bloco e seu conteúdo for LTR, o texto será exibido no lado esquerdo do contêiner, mesmo que esteja em torno de RTL. Isso pode ser curado simplesmente pela definição explícita text-align: right . Você pode até aplicar isso a toda a página na versão em árabe - o valor dessa propriedade é herdado.


Os ícones também não são espelhados automaticamente. E sem isso, os ícones direcionais, como setas na galeria, podem olhar na direção errada. Imagine que este é o único caso em que flechas feitas através de bordas se justificam!


Uma simples transformação ajudará a refletir os ícones:


 [dir="rtl"] .my-icon { transform: scaleX(-1); } 

É verdade que não ajudará se o ícone contiver letras ou números. Então você deve criar dois ícones diferentes e colá-los condicionalmente:


imagem


E, no entanto, acontece que nem todos os elementos da interface precisam ser espelhados. Por exemplo, no nosso caso, decidimos deixar as caixas de seleção normais:


imagem


imagem


Não sei como selecionar esses elementos em toda a interface. Somente um falante nativo pode ajudar aqui, quem dirá o que lhe é familiar e o que não é.


Entrada do usuário


Mesmo se controlarmos completamente todos os dados de nosso aplicativo, eles poderão ser inseridos pelo usuário. Por exemplo, o nome do arquivo. Você pode até inventar e fornecer o arquivo .js como .png - essa vulnerabilidade estava no Telegram :


cool_picture * U + 202E * gnp.js → cool_picture sj.png

Nesses casos, vale a pena filtrar caracteres utf inadequados da string.


Lançando scripts


A sintaxe muda um pouco no javascript RTL. O loop que se parecia com isso:


 for (let i = 0; i < arr.length; i++) { 

Agora você precisa escrever assim:


 for (++i; length.arr > i; let 0 = i) { 

Uma piada.


Tudo que você precisa fazer é evitar os conceitos de "esquerda" e "direita" no seu código. Por exemplo, encontramos problemas no cálculo das coordenadas do centro da tela - antes de todos os cartões ficarem à esquerda e agora à direita, mas o código do aplicativo não sabia disso. Todos os cálculos e estilos em linha devem ser realizados, levando em consideração o foco básico.


Raciocínio rápido


Em algumas situações, é difícil implementar o suporte à RTL em algum componente do sistema. Então você precisa tentar adaptar esse componente para RTL do lado de fora, mas deixe o LTR dentro.


Por exemplo, temos um controle deslizante. Ele suporta apenas valores ordenados positivos. Pode ser linear e logarítmico (no início, a densidade dos valores é menor que no final). Você precisa refleti-lo enquanto mantém o comportamento da balança.


imagem


Você pode virar o controle deslizante usando transform: scaleX(-1) . Então você deve inverter o trabalho com o mouse (cliques e arrastos) em relação ao centro do controle deslizante. Opção ruim.


Há outra opção - girar o eixo na outra direção, alterando apenas a transferência e o recebimento de valores do controle deslizante. Se for uma escala linear, em vez do conjunto de valores [10, 100, 1000], transferiremos o conjunto [N-1000, N-100, N-10] e, no manipulador, o converteremos novamente. Para a escala logarítmica, em vez do conjunto [10, 100, 1000], passamos [1/1000, 1/100, 1/10]:


 function flipSliderValues(values, scale, isRtl) { if (!isRtl) { return values; } if (scale === 'log') { // [A, B] --> [1/B, 1/A] return values.map(x => 1 / x).reverse(); } // [A, B] --> [MAX-B, MAX-A] return values.map(x => Number.MAX_SAFE_INTEGER - x).reverse(); }; 

Foi assim que o controle deslizante começou a dar suporte ao RTL, embora ele próprio não saiba disso.


Livro de histórias


Ao contrário da composição tipográfica no IE9, você não precisa executar um navegador separado para verificar a tipografia em RTL. Você pode até escrever e ver o layout de LTR e RTL simultaneamente em uma janela. Para fazer isso, você pode, por exemplo, criar um decorador no livro de histórias , que renderiza duas versões da história de uma só vez:


imagem


A captura de tela mostra que, sem o isolamento text-overflow: ellipsis não se comportam como gostaríamos - é melhor corrigi-lo imediatamente.


É muito mais fácil oferecer suporte à RTL imediatamente durante o layout do que testar absolutamente todo o projeto posteriormente.


Problemas não resolvidos


O conhecimento da teoria não ajuda a resolver absolutamente todos os problemas. Eu darei um exemplo.


Uma dica que aparece na direção do texto enquanto você digita. Quando falamos sobre entrada multilíngue, não podemos saber antecipadamente qual direção exibir esse prompt:




Você precisa tentar evitar esses problemas no estágio de design e, às vezes, abandonar soluções que são óbvias para o LTR e não são aplicáveis ​​no RTL. Nesse caso, ao navegar pelos prompts, você pode substituir o texto inteiro (como, por exemplo, Yandex ou Google).


Conclusões


RTL não é apenas "mudar tudo"


É necessário levar em conta as peculiaridades da linguagem, você não precisa inverter alguma coisa, precisa adaptar outra coisa. Em algum lugar da lógica é absolutamente necessário abandonar a "direita" / "esquerda".


É muito difícil fazer algo sem conhecer o idioma


Você pensará que está tudo pronto até mostrar seu projeto a um falante nativo real. Desenvolva do ponto de vista de uma pessoa que não conhece nenhum idioma. Afinal, mesmo essas palavras óbvias para você, como, por exemplo, “Twitter”, podem ter que ser traduzidas. E os sinais de pontuação, ao que parece, não são os mesmos em todo o planeta.


Receita final


É difícil descrever em uma lista tudo o que foi discutido em dois artigos. Não haverá explicação, mas aqui está a principal coisa a fazer:


  • certifique-se de encontrar um falante nativo e mostrar os protótipos o mais cedo possível;
  • Colete estilos para LTR e RTL em arquivos diferentes. Para isso, rtlcss e webpack-rtl-plugin são adequados;
  • adicione exceções para tudo o que não precisa ser revertido e reflita claramente o que não foi revertido;
  • isole todo o conteúdo arbitrário usando <bdi> e dir="auto" ;
  • defina explicitamente o text-align para a página inteira;
  • Evite left / right no código js quando você quer dizer início e fim.

Prepare-se para gastar mais tempo com os menores detalhes.


Não é difícil estar preparado com antecedência


E algumas dicas para quem ainda não vai adaptar o site à RTL, mas quer colocar um canudo:


  • não use a propriedade direction para outros fins;
  • por precaução, ainda isole todo o conteúdo arbitrário (e, de fato, mesmo na interface em inglês, os usuários podem escrever algo em árabe e quebrar tudo);
  • se possível, use propriedades lógicas de css ;
  • verifique o layout não apenas em navegadores diferentes, mas também em RTL, pelo menos por curiosidade. Melhor controle discreto do layout em RTL usando ferramentas como um livro de histórias ;
  • não permita construções de linguagem de código fixo (por exemplo, concatenação de seqüências de caracteres separadas por vírgulas); se possível, configure tudo, incluindo sinais de pontuação. Isso é útil não apenas para RTL - por exemplo, em grego, o ponto de interrogação é ";".

Essas regras não devem ser um aborrecimento. Mas se de repente surgir o desejo de lançar a versão RTL, ela ficará muito mais barata do que poderia.

Source: https://habr.com/ru/post/pt416635/


All Articles